first commit
This commit is contained in:
261
Seg_All_In_One_YoloModel/Yolo数据集构建/0_1_check_picture_pair.py
Normal file
261
Seg_All_In_One_YoloModel/Yolo数据集构建/0_1_check_picture_pair.py
Normal file
@@ -0,0 +1,261 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Set, Dict, Tuple
|
||||
|
||||
# <--- 新增: 导入 Pillow 库用于图像处理 ---
|
||||
try:
|
||||
from PIL import Image
|
||||
# 禁用解压缩炸弹检查,以防万一标签图非常大且简单
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
except ImportError:
|
||||
print("错误: 本脚本需要 'Pillow' 库来检查图像尺寸。", file=sys.stderr)
|
||||
print("请运行: pip install Pillow", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# --- 新增结束 ---
|
||||
|
||||
|
||||
# 定义支持的文件扩展名
|
||||
VALID_EXTENSIONS = {'.png', '.jpg', '.jpeg'}
|
||||
|
||||
def process_directory(path: Path,
|
||||
prefix: str = "",
|
||||
suffix: str = "") -> Tuple[Set[str], Dict[str, Path], Dict[Tuple[int, int], int]]: # <--- 修改:更新了返回类型
|
||||
"""
|
||||
处理单个目录,提取所有有效文件的文件名(stem),并根据前缀和后缀进行规范化。
|
||||
同时统计所有图像的尺寸。
|
||||
|
||||
返回:
|
||||
normalized_stems (Set[str]): 规范化处理后的文件名集合。
|
||||
normalized_to_full_path (Dict[str, Path]): 规范化文件名 -> 原始完整路径 的映射。
|
||||
size_counts (Dict[Tuple[int, int], int]): (宽度, 高度) -> 数量 的映射。 # <--- 新增
|
||||
"""
|
||||
if not path.is_dir():
|
||||
print(f"错误: 路径 '{path}' 不是一个有效的目录。", file=sys.stderr)
|
||||
return set(), {}, {} # <--- 修改
|
||||
|
||||
normalized_stems: Set[str] = set()
|
||||
normalized_to_full_path: Dict[str, Path] = {}
|
||||
size_counts: Dict[Tuple[int, int], int] = {} # <--- 新增:用于统计尺寸
|
||||
|
||||
for file_path in path.glob('*'):
|
||||
# 确保是文件,并且扩展名在我们的有效列表中
|
||||
if file_path.is_file() and file_path.suffix.lower() in VALID_EXTENSIONS:
|
||||
|
||||
# <--- 新增: 尝试获取图像尺寸 ---
|
||||
try:
|
||||
# 使用 with...as... 确保文件被正确关闭
|
||||
with Image.open(file_path) as img:
|
||||
size: Tuple[int, int] = img.size
|
||||
size_counts[size] = size_counts.get(size, 0) + 1
|
||||
except Exception as e:
|
||||
print(f"警告: 无法读取图像 '{file_path.name}' 的尺寸. 错误: {e}", file=sys.stderr)
|
||||
# --- 尺寸获取结束 ---
|
||||
|
||||
original_stem = file_path.stem
|
||||
normalized_stem = original_stem
|
||||
|
||||
# 仅当提供了前缀/后缀时才进行处理
|
||||
if prefix and normalized_stem.startswith(prefix):
|
||||
normalized_stem = normalized_stem[len(prefix):]
|
||||
|
||||
if suffix and normalized_stem.endswith(suffix):
|
||||
normalized_stem = normalized_stem[:-len(suffix)]
|
||||
|
||||
# 检查处理后是否重名,如果重名则发出警告
|
||||
if normalized_stem in normalized_to_full_path:
|
||||
print(f"警告: 规范化后文件名发生冲突。")
|
||||
print(f" '{file_path.name}' 和 '{normalized_to_full_path[normalized_stem].name}' 都变成了 '{normalized_stem}'")
|
||||
|
||||
normalized_stems.add(normalized_stem)
|
||||
normalized_to_full_path[normalized_stem] = file_path
|
||||
|
||||
return normalized_stems, normalized_to_full_path, size_counts # <--- 修改
|
||||
|
||||
|
||||
# <--- 新增: 打印尺寸统计的辅助函数 ---
|
||||
def print_size_report(title: str, size_counts: Dict[Tuple[int, int], int]):
|
||||
"""
|
||||
格式化并打印尺寸统计报告。
|
||||
"""
|
||||
print(f"\n{title}")
|
||||
if not size_counts:
|
||||
print(" (未找到或无法读取任何图像文件)")
|
||||
return
|
||||
|
||||
total_files = sum(size_counts.values())
|
||||
print(f" 总共 {total_files} 个文件,分布如下:")
|
||||
|
||||
# 按尺寸 (宽, 高) 排序输出
|
||||
for size, count in sorted(size_counts.items()):
|
||||
width, height = size
|
||||
print(f" - 尺寸 (宽, 高) {width}x{height}: {count} 个文件")
|
||||
# --- 新增结束 ---
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="比较两个文件夹中的文件名是否匹配,并可选择删除不匹配项。")
|
||||
parser.add_argument("-i", "--image",
|
||||
type=Path,
|
||||
default="./ORI",
|
||||
help="Image 文件夹路径")
|
||||
parser.add_argument("-l", "--label",
|
||||
type=Path,
|
||||
default="./Label",
|
||||
help="Label 文件夹路径")
|
||||
parser.add_argument("-p", "--prefix",
|
||||
type=str,
|
||||
default="",
|
||||
help="在 Label 文件名中要忽略的前缀")
|
||||
parser.add_argument("-s", "--suffix",
|
||||
type=str,
|
||||
default="",
|
||||
help="在 Label 文件名中要忽略的后缀")
|
||||
parser.add_argument("-y", "--yes",
|
||||
action="store_true",
|
||||
help="自动确认删除所有不匹配的文件,跳过交互式提示。")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 1. 处理 Image 文件夹
|
||||
print(f"--- 正在处理 Image 文件夹: {args.image} ---")
|
||||
image_stems, image_norm_to_path, image_size_counts = process_directory(args.image) # <--- 修改
|
||||
if not image_stems:
|
||||
print(f"未在 Image 文件夹中找到任何 .png 或 .jpg 文件。")
|
||||
# <--- 新增: 打印 Image 尺寸报告 ---
|
||||
print_size_report("--- Image 文件夹尺寸统计 ---", image_size_counts)
|
||||
|
||||
# 2. 处理 Label 文件夹
|
||||
print(f"\n--- 正在处理 Label 文件夹: {args.label} ---")
|
||||
print(f"(忽略前缀: '{args.prefix}', 忽略后缀: '{args.suffix}')")
|
||||
label_stems_normalized, label_norm_to_path, label_size_counts = process_directory( # <--- 修改
|
||||
args.label,
|
||||
args.prefix,
|
||||
args.suffix
|
||||
)
|
||||
if not label_stems_normalized:
|
||||
print(f"未在 Label 文件夹中找到任何 .png 或 .jpg 文件。")
|
||||
# <--- 新增: 打印 Label 尺寸报告 ---
|
||||
print_size_report("--- Label 文件夹尺寸统计 ---", label_size_counts)
|
||||
|
||||
|
||||
# 3. 执行比较 (使用集合运算)
|
||||
matching_stems = image_stems.intersection(label_stems_normalized)
|
||||
extra_in_image = image_stems.difference(label_stems_normalized)
|
||||
extra_in_label_normalized = label_stems_normalized.difference(image_stems)
|
||||
extra_in_label_original_stems = {label_norm_to_path[stem].stem for stem in extra_in_label_normalized}
|
||||
|
||||
# 4. 输出结果
|
||||
print("\n" + "="*30)
|
||||
print(" 匹配结果报告")
|
||||
print("="*30)
|
||||
|
||||
print(f"\n匹配的文件总数: {len(matching_stems)}")
|
||||
|
||||
# <--- 新增: 检查匹配项的尺寸是否一致 ---
|
||||
print("\n--- 正在检查匹配文件的尺寸 ---")
|
||||
mismatched_size_pairs = []
|
||||
if not matching_stems:
|
||||
print("(无匹配文件,跳过尺寸检查)")
|
||||
else:
|
||||
print(f"正在检查 {len(matching_stems)} 对文件...")
|
||||
for stem in sorted(matching_stems):
|
||||
try:
|
||||
img_path = image_norm_to_path[stem]
|
||||
lbl_path = label_norm_to_path[stem]
|
||||
|
||||
# 再次打开以比较尺寸
|
||||
with Image.open(img_path) as img:
|
||||
img_size = img.size
|
||||
with Image.open(lbl_path) as lbl:
|
||||
lbl_size = lbl.size
|
||||
|
||||
if img_size != lbl_size:
|
||||
mismatched_size_pairs.append((img_path, img_size, lbl_path, lbl_size))
|
||||
|
||||
except Exception as e:
|
||||
print(f" 错误: 无法比较 '{stem}' 的尺寸. 错误: {e}", file=sys.stderr)
|
||||
|
||||
if not mismatched_size_pairs:
|
||||
print(f"√ 成功: 所有 {len(matching_stems)} 对匹配文件均具有相同的尺寸。")
|
||||
else:
|
||||
print(f"\n!!! 警告: 发现 {len(mismatched_size_pairs)} 对文件尺寸不匹配:")
|
||||
for img_path, img_size, lbl_path, lbl_size in mismatched_size_pairs:
|
||||
print(f" - [Image] {img_path.name} {img_size} != [Label] {lbl_path.name} {lbl_size}")
|
||||
# --- 尺寸检查结束 ---
|
||||
|
||||
print("\n--- Image 文件夹中多余的文件 (共 {} 个) ---".format(len(extra_in_image)))
|
||||
if not extra_in_image:
|
||||
print("(无)")
|
||||
else:
|
||||
for file_stem in sorted(extra_in_image):
|
||||
print(file_stem)
|
||||
|
||||
print("\n--- Label 文件夹中多余的文件 (共 {} 个) ---".format(len(extra_in_label_original_stems)))
|
||||
print("(显示的是原始文件名,非规范化名称)")
|
||||
if not extra_in_label_original_stems:
|
||||
print("(无)")
|
||||
else:
|
||||
for file_stem in sorted(extra_in_label_original_stems):
|
||||
print(file_stem)
|
||||
|
||||
print("\n" + "="*30)
|
||||
|
||||
# 5. 删除交互部分 (无修改)
|
||||
if not extra_in_image and not extra_in_label_normalized:
|
||||
print("\n所有文件均完美匹配,无需删除。")
|
||||
# <--- 新增: 如果文件名匹配,但尺寸不匹配,也在这里提醒一下 ---
|
||||
if mismatched_size_pairs:
|
||||
print("注意:虽然文件名匹配,但有 {} 对文件的尺寸不一致,请检查上面的报告。".format(len(mismatched_size_pairs)))
|
||||
return
|
||||
|
||||
# ... (后续的删除逻辑保持不变) ...
|
||||
confirm_delete = False
|
||||
if args.yes:
|
||||
print("\n检测到 -y/--yes 参数,将自动删除不匹配的文件。")
|
||||
confirm_delete = True
|
||||
else:
|
||||
try:
|
||||
choice = input("\n警告: 是否要永久删除所有上述 '多余' 的文件? (y/n): ").strip().lower()
|
||||
if choice in ['y', 'yes']:
|
||||
confirm_delete = True
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\n操作被用户中断。")
|
||||
confirm_delete = False
|
||||
|
||||
if confirm_delete:
|
||||
print("\n--- 正在删除 Image 文件夹中的多余文件 ---")
|
||||
deleted_count = 0
|
||||
for stem in sorted(extra_in_image):
|
||||
file_path = image_norm_to_path[stem]
|
||||
try:
|
||||
file_path.unlink()
|
||||
print(f" 已删除: {file_path.name}")
|
||||
deleted_count += 1
|
||||
except OSError as e:
|
||||
print(f" 删除失败: {file_path.name} (错误: {e})", file=sys.stderr)
|
||||
print(f"--- Image 文件夹共删除 {deleted_count} 个文件 ---")
|
||||
|
||||
print("\n--- 正在删除 Label 文件夹中的多余文件 ---")
|
||||
deleted_count = 0
|
||||
for norm_stem in sorted(extra_in_label_normalized):
|
||||
file_path = label_norm_to_path[norm_stem]
|
||||
try:
|
||||
file_path.unlink()
|
||||
print(f" 已删除: {file_path.name} (原始文件名)")
|
||||
deleted_count += 1
|
||||
except OSError as e:
|
||||
print(f" 删除失败: {file_path.name} (错误: {e})", file=sys.stderr)
|
||||
print(f"--- Label 文件夹共删除 {deleted_count} 个文件 ---")
|
||||
|
||||
print("\n删除操作完成。")
|
||||
|
||||
else:
|
||||
print("\n操作已取消。未删除任何文件。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
122
Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_TOOL_stack_pics.sh
Normal file
122
Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_TOOL_stack_pics.sh
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/bin/bash
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 -i <ori_image_directory> -l <ori_label_directory> -r <stack_result_directory> [ -a <alpha> -p <prefix> -s <suffix> -h]"
|
||||
echo "对image图片和label图片进行匹配(-i、-l -r均不能为空)(-p -s默认为空"" -a默认为\"0.3\") "
|
||||
echo "-i:原始image的路径,-l:原始label的路径,-p:前缀内容,-s:后缀内容(不用管文件后缀名),-h:帮助"
|
||||
echo "e.g. bash 0_2_TOOL_stack_pics.sh -i ./ori -l ./label -r ./result_0.3透明度 -a 0.3 -p Prefix -s _label"
|
||||
}
|
||||
|
||||
ori_image_directorys=""
|
||||
ori_label_directorys=""
|
||||
stack_result_directorys=""
|
||||
prefix=""
|
||||
suffix=""
|
||||
alpha="0.3"
|
||||
|
||||
while getopts "hl:i:r:p:s:a:" opt; do
|
||||
case $opt in
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
i)
|
||||
ori_image_directorys=$OPTARG
|
||||
;;
|
||||
l)
|
||||
ori_label_directorys=$OPTARG
|
||||
;;
|
||||
p)
|
||||
prefix=$OPTARG
|
||||
;;
|
||||
s)
|
||||
suffix=$OPTARG
|
||||
;;
|
||||
r)
|
||||
stack_result_directorys=$OPTARG
|
||||
;;
|
||||
a)
|
||||
alpha=$OPTARG
|
||||
;;
|
||||
*)
|
||||
echo -e '\033[31m!!! Error, Illegal input !!!\033[0m'
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 判断输入地址是否为空
|
||||
if [ -z "$ori_label_directorys" ] || [ -z "$ori_image_directorys" ] || [ -z "$stack_result_directorys" ]; then
|
||||
echo -e "\033[31m输入地址 -i -l -z 存在空地址\033[0m"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 地址转化
|
||||
ori_image_directory=$(readlink -f "$ori_image_directorys")
|
||||
ori_label_directory=$(readlink -f "$ori_label_directorys")
|
||||
stack_result_directory=$(readlink -f "$stack_result_directorys")
|
||||
if [ -z "$ori_label_directory" ] || [ -z "$ori_image_directory" ]|| [ -z "$stack_result_directory" ]; then
|
||||
echo "image、label、result存在无法解析地址,程序退出"
|
||||
echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys"
|
||||
echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys"
|
||||
echo -e "\033[31mori_label_directory\033[0m: $stack_result_directorys"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$ori_label_directory" ] || [ ! -d "$ori_image_directory" ]; then
|
||||
echo "image、label两目录有一个不存在,程序退出"
|
||||
echo -e "\033[31mori_image_directory\033[0m: $ori_image_directory"
|
||||
echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 获取当前脚本的路径和名称
|
||||
script_path=$(dirname "$0")
|
||||
# 将当前目录更改为脚本所在的路径
|
||||
cd "$script_path"
|
||||
|
||||
# 激活conda环境
|
||||
source /home/"$USER"/miniconda/bin/activate Deal_Data
|
||||
|
||||
echo -e "\033[32m_____ 0_2_TOOL_stack_pics.sh _____\033[0m"
|
||||
echo -e "\033[33mimage所在文件夹为$ori_image_directory\nlable所在文件夹为$ori_label_directory\033[0m"
|
||||
# 遍历label目录
|
||||
for file_path in "$ori_label_directory"/*; do
|
||||
# 判断是否是文件
|
||||
if [[ -f "$file_path" ]]; then
|
||||
file_name=$(basename "$file_path")
|
||||
# 判断文件名是否符合规范
|
||||
if [[ "$file_name" =~ .*\.(jpg|png|bmp|JPG|PNG|BMP) ]]; then # 判断是否有为图片
|
||||
# if [[ "$file_name" =~ "$prefix".*"$suffix".*\.(jpg|png|bmp|JPG|PNG|BMP)$ ]]; then # 判断是否有满足要求的文件名
|
||||
# 抽取文件名(有前缀、后缀的抽取前缀、后缀里面的,没有的返回整个)
|
||||
if [ -z $prefix ];then
|
||||
file_name_extract=$(echo $file_name | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/")
|
||||
else
|
||||
file_name_extract=$(echo $file_name | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/")
|
||||
fi
|
||||
# 从label目录中看是否有此文件
|
||||
file_name_other=$(ls $ori_image_directory | grep "^${file_name_extract}\.")
|
||||
file_name_other=$(echo "$(echo "$file_name_other" | sed '/^$/d')" | head -n1) # 提取出文件名
|
||||
# 如果另一个目录没有此文件的话
|
||||
if [ -z "$file_name_other" ]; then
|
||||
echo "$file_name label中对应内容未在$ori_image_directory搜索到"
|
||||
# 建立相关存储文件夹
|
||||
if [ ! -d "$ori_label_directory/Not_pair_pics" ]; then
|
||||
mkdir -p "$ori_label_directory/Not_pair_pics" # 建立存储文件夹
|
||||
fi
|
||||
# 移动相关文件
|
||||
cp "$ori_label_directory/$file_name" "$ori_label_directory/Not_pair_pics"
|
||||
echo "$file_name" >> "$ori_label_directory/Not_pair_pics/not_pair.txt"
|
||||
else # 如果另一个目录有此配对文件的话,则运行相关程序
|
||||
echo "image中的$file_name_other,与lable中的$file_name"
|
||||
mkdir -p "$stack_result_directory"
|
||||
python 0_2_stack_picture.py "$ori_image_directory/$file_name_other" "$ori_label_directory/$file_name" "$stack_result_directory" "$alpha"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
fi
|
||||
else
|
||||
echo "$file_path不是文件"
|
||||
fi
|
||||
done
|
||||
41
Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_stack_picture.py
Normal file
41
Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_stack_picture.py
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
import cv2, os, sys
|
||||
|
||||
def Stack_pic(Background_path, Overlay_path, Result_dir, alpha=0.3):
|
||||
# 读取两张没有alpha通道的图片
|
||||
img1 = cv2.imread(Background_path) # 底层图片
|
||||
img2 = cv2.imread(Overlay_path) # 顶层图片
|
||||
|
||||
Result_name = os.path.splitext(os.path.basename(Background_path))[0]
|
||||
|
||||
# 将img2调整为与img1大小相同
|
||||
img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0]))
|
||||
|
||||
# 将img2的透明度调整为20%
|
||||
overlay_alpha = alpha
|
||||
|
||||
# 将img2叠加到img1上
|
||||
overlay = cv2.addWeighted(img1, 1 - overlay_alpha, img2, overlay_alpha, 0)
|
||||
|
||||
# 保存结果
|
||||
if not os.path.exists(Result_dir):
|
||||
os.makedirs(Result_dir)
|
||||
cv2.imwrite(os.path.join(Result_dir, Result_name+'.png'), overlay)
|
||||
print("堆叠图片写入地址:", os.path.join(Result_dir, Result_name+'.png'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Background_path = sys.argv[1] # 背景所在路径
|
||||
Overlay_path = sys.argv[2] # 上层图片所在路径
|
||||
Result_dir = sys.argv[3] # 结果所在目录
|
||||
# 透明度,默认为0.3
|
||||
try:
|
||||
alpha = float(sys.argv[4])
|
||||
if(alpha > 1 or alpha < 0):
|
||||
print("alpha 透明度输入不正确,其值应该在0~1之间")
|
||||
alpha = 0.3
|
||||
except:
|
||||
alpha = 0.3
|
||||
# 进行对叠程序
|
||||
Stack_pic(Background_path, Overlay_path, Result_dir, alpha)
|
||||
442
Seg_All_In_One_YoloModel/Yolo数据集构建/1_deal_labels.py
Normal file
442
Seg_All_In_One_YoloModel/Yolo数据集构建/1_deal_labels.py
Normal file
@@ -0,0 +1,442 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*
|
||||
import os,time,sys,threading, colorsys, argparse
|
||||
import asyncio, cv2, multiprocessing, random
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from Tool_deal_labels import edge_detection, detect_connected_regions, Tool_color_connected_array, fill_white_regions, color_connected_regions
|
||||
from Tool_Classes_And_Palette import Annotate_CLASSES, Annotate_PALETTE, bg_PALETTE
|
||||
|
||||
def getFileList(dir,Filelist=[], ext=None, Max_layer=1, layer=0, Donot_Search=['1_边缘检测并膨胀', '2_连通区域检测', '3_分水岭算法填充']):
|
||||
"""
|
||||
获取文件夹及其子文件夹中文件列表
|
||||
输入 dir:文件夹根目录
|
||||
输入 ext: 扩展名
|
||||
返回: 文件路径列表
|
||||
"""
|
||||
newDir = dir
|
||||
if os.path.isfile(dir):
|
||||
if ext is None:
|
||||
Filelist.append(dir)
|
||||
else:
|
||||
if ext in dir[-3:]:
|
||||
Filelist.append(dir)
|
||||
|
||||
elif os.path.isdir(dir):
|
||||
file_name = os.path.basename(dir)
|
||||
# 判断是否在禁搜名单中
|
||||
if file_name in Donot_Search:
|
||||
return Filelist
|
||||
for s in os.listdir(dir):
|
||||
newDir=os.path.join(dir,s)
|
||||
if layer <= Max_layer:
|
||||
getFileList(newDir, Filelist, ext, Max_layer, layer+1)
|
||||
|
||||
return Filelist
|
||||
|
||||
class Deal_image():
|
||||
def __init__(self, Annotate_CLASSES = ('肝脏','胆囊'), Annotate_PALETTE = [[255,91,0],[255,234,0]], src_label_fold = "./Label", save_pro_label_fold = "./LABEL_PNG_new", save_GT_label_fold = "./Label_Generate", GT_channel = 1, pro_append_name="_label", GT_append_name="", ori_img_folder="./ORI_PNG", res_label_folder="./Result_label", save_merge_pic_folder="./Result_merge", back_gnd_color=0, first_class_color=1, pic_type="png", Max_width = 10000, Label_Max_Search_layer=1000, save_process_pics=False, bg_PALETTE = [0,0,0]):
|
||||
# 背景最好放在最后
|
||||
# self.src_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','背景')
|
||||
# self.src_PALETTE = np.array([[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66],[0,0,0]])
|
||||
# self.src_CLASSES_NUM = np.shape(self.src_CLASSES)[0]
|
||||
self.bg_PALETTE = bg_PALETTE # 背景颜色 TODO
|
||||
|
||||
self.Annotate_CLASSES = Annotate_CLASSES # 待分类的类
|
||||
self.Annotate_PALETTE = np.array(Annotate_PALETTE) # 每一类的像素直
|
||||
self.Annotate_CLASSES_NUM = np.shape(Annotate_CLASSES)[0] # 类数量
|
||||
|
||||
self.save_process_pics = save_process_pics # 保存中间过程图片
|
||||
|
||||
self.src_label_fold = src_label_fold # 原始标签图片 保存位置
|
||||
self.save_pro_label_fold = save_pro_label_fold # 优化后标签图片 保存位置
|
||||
self.save_GT_label_fold = save_GT_label_fold # GT标签图片 保存位置
|
||||
|
||||
self.ori_img_folder = ori_img_folder # 最原始手术图片 保存位置
|
||||
self.res_label_folder = res_label_folder # 训练出来的label 保存位置
|
||||
self.save_merge_pic_folder = save_merge_pic_folder # 融合图像保存位置
|
||||
|
||||
self.pro_append_name = pro_append_name # 优化后标签图片后缀
|
||||
self.GT_append_name = GT_append_name # GT标签图片后缀
|
||||
self.GT_channel = GT_channel # GT标签图片通道数
|
||||
|
||||
self.Max_width = Max_width # 最大图片宽度(匹配时候用)
|
||||
self.pic_type = pic_type # 图片类型
|
||||
self.back_gnd_color = back_gnd_color # 背景颜色
|
||||
self.first_class_color = first_class_color # 第一类上的颜色
|
||||
self.Label_Max_Search_layer=Label_Max_Search_layer # 文件夹最大搜索深度
|
||||
try:
|
||||
self.labellist_src = getFileList(src_label_fold, [], pic_type, self.Label_Max_Search_layer)
|
||||
print('本次执行检索到ori_label图片 '+str(len(self.labellist_src))+' 张图像')
|
||||
except:
|
||||
self.labellist_src = None
|
||||
print("没有ori_label相关文件")
|
||||
|
||||
try:
|
||||
# print(save_pro_label_fold)
|
||||
self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer)
|
||||
print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像')
|
||||
except:
|
||||
self.labellist_pro = None
|
||||
print("没有pro_label相关文件")
|
||||
|
||||
try:
|
||||
self.imglist_src = getFileList(ori_img_folder, [], pic_type, self.Label_Max_Search_layer)
|
||||
self.reslist_src = getFileList(res_label_folder, [], pic_type, self.Label_Max_Search_layer)
|
||||
print('本次执行检索到ori原始图片 '+str(len(self.imglist_src))+' 张图像')
|
||||
print('本次执行检索到训练train_result图片 '+str(len(self.reslist_src))+' 张图像')
|
||||
except:
|
||||
self.imglist_src = None
|
||||
self.reslist_src = None
|
||||
print("没有train_result和原始图片相关文件")
|
||||
|
||||
# 获取单张图片各个通路信息
|
||||
def get_single_pic_rgb(self, imgpath):
|
||||
print(imgpath)
|
||||
image = Image.open(imgpath).convert('RGB') # 转为RGB图片
|
||||
# 将 RGB 色值分离
|
||||
image.load()
|
||||
r, g, b = image.split()
|
||||
r = np.array(r)
|
||||
g = np.array(g)
|
||||
b = np.array(b)
|
||||
return image, r, g, b
|
||||
|
||||
# 将单个pro图片变成GT图片
|
||||
def Conver_pro_label_pic_2_GT_pic(self, imgpath, imgname):
|
||||
time_start=time.time() # 记录开始时间
|
||||
# 获取单张图片各个通路信息
|
||||
image, r,g,b = self.get_single_pic_rgb(imgpath)
|
||||
|
||||
result_gt = np.ones(np.shape(image))*self.back_gnd_color # 初始化填充内容为back_gnd_color
|
||||
gt_number = self.first_class_color # 第一类上色颜色确定
|
||||
|
||||
# PALETTE中排除掉 '背景' [0,0,0]
|
||||
PALETTE_No_Bg = self.Annotate_PALETTE[~np.all(self.Annotate_PALETTE == self.bg_PALETTE, axis=1)]
|
||||
|
||||
# 遍历所有待识别颜色
|
||||
for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in PALETTE_No_Bg:
|
||||
# 查找三原色匹配位置
|
||||
locate_r = np.where( r == Annotate_PALETTE_r )
|
||||
locate_g = np.where( g == Annotate_PALETTE_g )
|
||||
locate_b = np.where( b == Annotate_PALETTE_b )
|
||||
|
||||
# 查找都匹配位置(交集)
|
||||
# 将矩阵换一种表示形式
|
||||
locate_r = np.array(locate_r[0]) * self.Max_width + np.array(locate_r[1])
|
||||
locate_g = np.array(locate_g[0]) * self.Max_width + np.array(locate_g[1])
|
||||
locate_b = np.array(locate_b[0]) * self.Max_width + np.array(locate_b[1])
|
||||
|
||||
# 用自带函数寻找匹配项
|
||||
matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b)
|
||||
matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0)
|
||||
result_gt[matched[0],matched[1], :] = gt_number
|
||||
gt_number = gt_number + 1
|
||||
|
||||
# 输出GT图片
|
||||
if(int(self.GT_channel) == 1):
|
||||
result_gt = result_gt[:,:,0]
|
||||
elif(int(self.GT_channel) == 3):
|
||||
result_gt = cv2.cvtColor(np.float32(result_gt), cv2.COLOR_RGB2BGR) # rgb颜色互换
|
||||
else:
|
||||
print("GT_channel 必须为1或3")
|
||||
quit
|
||||
try: # 新建文件夹
|
||||
os.mkdir(self.save_GT_label_fold)
|
||||
except:
|
||||
print("已有"+self.save_GT_label_fold)
|
||||
if imgname.lower().endswith(('.jpg', '.png')):
|
||||
save_dir = os.path.join(self.save_GT_label_fold, os.path.basename(imgname).rpartition('.')[0]+self.GT_append_name+'.'+self.pic_type)
|
||||
else:
|
||||
save_dir = os.path.join(self.save_GT_label_fold, os.path.basename(imgname)+self.GT_append_name+'.'+self.pic_type)
|
||||
cv2.imwrite(save_dir, result_gt)
|
||||
print("GT图片已保存", save_dir)
|
||||
time_end=time.time() # 输出结束时间
|
||||
print('time cost',time_end-time_start,'s')
|
||||
|
||||
# 将处理好的图片转化为GT图片
|
||||
def Conver_pro_label_pic_2_GT_pic_all(self):
|
||||
print("\033[33m**** 进行转换将Pro_label_pic转换为GT_label_pic ****\033[0m")
|
||||
print("\033[33mPro_label_pic存储位置为:\033[0m", self.save_pro_label_fold)
|
||||
print("\033[33mGT_label_pic生成位置为:\033[0m", self.save_GT_label_fold)
|
||||
try:
|
||||
# print(save_pro_label_fold)
|
||||
self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer)
|
||||
print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像')
|
||||
except:
|
||||
self.labellist_pro = None
|
||||
print("没有pro_label相关文件")
|
||||
try:
|
||||
os.mkdir(self.save_GT_label_fold) # 新建存储文件夹
|
||||
except:
|
||||
print("已有"+self.save_GT_label_fold)
|
||||
|
||||
# 指定最大进程数为 3
|
||||
max_processes = 20
|
||||
# 创建Pool对象
|
||||
pool = multiprocessing.Pool(processes=max_processes)
|
||||
# 创建并启动进程
|
||||
args_list1 = []
|
||||
args_list2 = []
|
||||
|
||||
# 遍历整个文件夹
|
||||
for imgpath in self.labellist_pro:
|
||||
imgname = os.path.basename(imgpath).rpartition('.')[0].replace(self.pro_append_name,"")
|
||||
args_list1.append(imgpath)
|
||||
args_list2.append(imgname)
|
||||
args_list = zip(args_list1, args_list2)
|
||||
# 使用进程池并行执行任务
|
||||
pool.starmap(self.Conver_pro_label_pic_2_GT_pic, args_list)
|
||||
# 关闭进程池
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
def Conver_ori_label_pic_2_pro_pic(self, imgpath, imgname):
|
||||
time_start=time.time() # 记录开始时间
|
||||
# 获取单张图片各个通路信息
|
||||
image = cv2.imread(imgpath)
|
||||
|
||||
# 1. 边缘检测并膨胀
|
||||
dilated_image = edge_detection(image)
|
||||
# 如果需要存储中间态图片
|
||||
if(self.save_process_pics == True):
|
||||
if imgname.lower().endswith(('.jpg', '.png')):
|
||||
save_dir = os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_Edge'+'.'+self.pic_type)
|
||||
else:
|
||||
save_dir = os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀', os.path.basename(imgname)+self.pro_append_name+'_Edge'+'.'+self.pic_type)
|
||||
cv2.imwrite(save_dir, dilated_image)
|
||||
print("中间态-边缘检测并膨胀 图片已保存", save_dir)
|
||||
time_end=time.time() # 输出结束时间
|
||||
print('time cost',time_end-time_start,'s')
|
||||
|
||||
# 2. 检测连通区域
|
||||
filtered_labeled_array, _ = detect_connected_regions(dilated_image)
|
||||
colored_image_filtered = Tool_color_connected_array(filtered_labeled_array)
|
||||
# 如果需要存储中间态图片
|
||||
if(self.save_process_pics == True):
|
||||
if imgname.lower().endswith(('.jpg', '.png')):
|
||||
save_dir = os.path.join(self.save_pro_label_fold, '2_连通区域检测', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_Region'+'.'+self.pic_type)
|
||||
else:
|
||||
save_dir = os.path.join(self.save_pro_label_fold, '2_连通区域检测', os.path.basename(imgname)+self.pro_append_name+'_Region'+'.'+self.pic_type)
|
||||
cv2.imwrite(save_dir, colored_image_filtered)
|
||||
print("中间态-连通区域检测 图片已保存", save_dir)
|
||||
time_end=time.time() # 输出结束时间
|
||||
print('time cost',time_end-time_start,'s')
|
||||
|
||||
# 3. 分水岭填充白色区域
|
||||
filled_labeled_array = fill_white_regions(filtered_labeled_array)
|
||||
colored_image_filled = Tool_color_connected_array(filled_labeled_array)
|
||||
# 如果需要存储中间态图片
|
||||
if(self.save_process_pics == True):
|
||||
if imgname.lower().endswith(('.jpg', '.png')):
|
||||
save_dir = os.path.join(self.save_pro_label_fold, '3_分水岭算法填充', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_FillEdge'+'.'+self.pic_type)
|
||||
else:
|
||||
save_dir = os.path.join(self.save_pro_label_fold, '3_分水岭算法填充', os.path.basename(imgname)+self.pro_append_name+'_FillEdge'+'.'+self.pic_type)
|
||||
cv2.imwrite(save_dir, colored_image_filled)
|
||||
print("中间态-分水岭算法填充 图片已保存", save_dir)
|
||||
time_end=time.time() # 输出结束时间
|
||||
print('time cost',time_end-time_start,'s')
|
||||
|
||||
# 4. 对连通区域最终上色
|
||||
ori_labeled_image = image
|
||||
result_pro = color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, self.Annotate_PALETTE)
|
||||
if imgname.lower().endswith(('.jpg', '.png')):
|
||||
save_dir = os.path.join(self.save_pro_label_fold, os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'.'+self.pic_type)
|
||||
else:
|
||||
save_dir = os.path.join(self.save_pro_label_fold, os.path.basename(imgname)+self.pro_append_name+'.'+self.pic_type)
|
||||
print("Pro图片已保存", save_dir)
|
||||
cv2.imwrite(save_dir, result_pro)
|
||||
time_end=time.time() # 输出结束时间
|
||||
print('time cost',time_end-time_start,'s')
|
||||
|
||||
# 将原始src图片转化为处理好的pro图片
|
||||
def Conver_ori_label_pic_2_pro_pic_all(self):
|
||||
print("\033[33m**** 进行转换将Ori_label_pic转换为Pro_label_pic ****\033[0m")
|
||||
print("\033[33mOri_label_pic存储位置为:\033[0m", self.src_label_fold)
|
||||
print("\033[33mPro_label_pic生成位置为:\033[0m", self.save_pro_label_fold)
|
||||
# 输出颜色预处理图片
|
||||
try:
|
||||
os.mkdir(self.save_pro_label_fold) # 新建存储文件夹
|
||||
except:
|
||||
print("已有"+self.save_pro_label_fold)
|
||||
if(self.save_process_pics == True):
|
||||
try:
|
||||
os.mkdir(os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀')) # 新建存储1_边缘检测并膨胀文件夹
|
||||
except:
|
||||
print("已有"+os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀'))
|
||||
try:
|
||||
os.mkdir(os.path.join(self.save_pro_label_fold, '2_连通区域检测')) # 新建存储2_连通区域检测文件夹
|
||||
except:
|
||||
print("已有"+os.path.join(self.save_pro_label_fold, '2_连通区域检测'))
|
||||
try:
|
||||
os.mkdir(os.path.join(self.save_pro_label_fold, '3_分水岭算法填充')) # 新建存储1_边缘检测并膨胀文件夹
|
||||
except:
|
||||
print("已有"+os.path.join(self.save_pro_label_fold, '3_分水岭算法填充'))
|
||||
|
||||
# 指定最大进程数为 20,多参数函数并行
|
||||
max_processes = 20
|
||||
# 创建Pool对象
|
||||
pool = multiprocessing.Pool(processes=max_processes)
|
||||
# 创建并启动进程
|
||||
args_list1 = []
|
||||
args_list2 = []
|
||||
|
||||
# 遍历整个文件夹
|
||||
for imgpath in self.labellist_src:
|
||||
if imgpath.lower().endswith(('.jpg', '.png')):
|
||||
imgname= os.path.basename(imgpath).rpartition('.')[0].replace(self.pro_append_name,"")
|
||||
else:
|
||||
imgname= os.path.basename(imgpath).replace(self.pro_append_name,"")
|
||||
try:
|
||||
print("Processing: ", imgname, "...")
|
||||
# self.Conver_ori_label_pic_2_pro_pic(imgpath, imgname)s
|
||||
# args_list.append({'imgpath': imgpath, 'imgname': imgname})
|
||||
args_list1.append(imgpath)
|
||||
args_list2.append(imgname)
|
||||
except:
|
||||
os.system("echo "+imgname+" >> error_1.txt")
|
||||
args_list = zip(args_list1, args_list2)
|
||||
# 使用进程池并行执行任务
|
||||
pool.starmap(self.Conver_ori_label_pic_2_pro_pic, args_list) # 使用starmap进行多参数并行
|
||||
# 关闭进程池
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
# 图片堆叠
|
||||
def Merge_ori_pic_and_label_pic(self, res_img_path, res_imgname):
|
||||
time_start=time.time() # 记录开始时间
|
||||
# 获取单张图片各个通路信息
|
||||
ori_img_path = os.path.join(self.ori_img_folder, res_imgname+'.'+self.pic_type)
|
||||
if not os.path.exists(ori_img_path):
|
||||
print("****照片不存在:****", ori_img_path)
|
||||
return -1
|
||||
ori_image, ori_r, ori_g, ori_b = self.get_single_pic_rgb(ori_img_path)
|
||||
res_image, res_r, res_g, res_b = self.get_single_pic_rgb(res_img_path)
|
||||
|
||||
merge_img = np.array(ori_image) # merge图片初始化,默认图片背景为0.0.0
|
||||
|
||||
# 遍历所有待识别颜色
|
||||
for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in self.Annotate_PALETTE:
|
||||
# 查找三原色匹配位置
|
||||
locate_r = np.where( res_r == Annotate_PALETTE_r )
|
||||
locate_g = np.where( res_g == Annotate_PALETTE_g )
|
||||
locate_b = np.where( res_b == Annotate_PALETTE_b )
|
||||
|
||||
# 查找都匹配位置(交集)
|
||||
# 将矩阵换一种表示形式
|
||||
locate_r = np.array(locate_r[0]) * self.Max_width + np.array(locate_r[1])
|
||||
locate_g = np.array(locate_g[0]) * self.Max_width + np.array(locate_g[1])
|
||||
locate_b = np.array(locate_b[0]) * self.Max_width + np.array(locate_b[1])
|
||||
|
||||
# 用自带函数寻找匹配项
|
||||
matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b)
|
||||
matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0)
|
||||
merge_img[matched[0],matched[1], 0] = Annotate_PALETTE_r
|
||||
merge_img[matched[0],matched[1], 1] = Annotate_PALETTE_g
|
||||
merge_img[matched[0],matched[1], 2] = Annotate_PALETTE_b
|
||||
|
||||
# 转成cv2形式
|
||||
merge_img = cv2.cvtColor(np.float32(merge_img), cv2.COLOR_RGB2BGR)
|
||||
|
||||
try: # 新建文件夹
|
||||
os.mkdir(self.save_merge_pic_folder)
|
||||
except:
|
||||
print("已有"+self.save_merge_pic_folder)
|
||||
if res_imgname.lower().endswith(('.jpg', '.png')):
|
||||
save_dir = os.path.join(self.save_merge_pic_folder, os.path.basename(res_imgname).rpartition('.')[0]+'.'+self.pic_type)
|
||||
else:
|
||||
save_dir = os.path.join(self.save_merge_pic_folder, os.path.basename(res_imgname)+'.'+self.pic_type)
|
||||
|
||||
|
||||
cv2.imwrite(save_dir, merge_img)
|
||||
print("Merge图片已保存", save_dir)
|
||||
time_end=time.time() # 输出结束时间
|
||||
print('time cost',time_end-time_start,'s')
|
||||
|
||||
# 将label图片与原图片重合
|
||||
def Merge_ori_pic_and_label_pic_all(self):
|
||||
# 遍历整个文件夹
|
||||
for res_img_path in self.reslist_src:
|
||||
if res_img_path.lower().endswith(('.jpg', '.png')):
|
||||
res_imgname = os.path.basename(res_img_path).rpartition('.')[0].replace(self.pro_append_name,"")
|
||||
else:
|
||||
res_imgname = os.path.basename(res_img_path).replace(self.pro_append_name,"")
|
||||
print("Processing: ", res_imgname, "...")
|
||||
self.Merge_ori_pic_and_label_pic(res_img_path, res_imgname)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# 创建参数解析器
|
||||
parser = argparse.ArgumentParser(description='Process some files.')
|
||||
# 添加参数选项
|
||||
parser.add_argument('-src_fold', dest='src_label_fold', default='./', help='source label folder')
|
||||
parser.add_argument('-save_pro_fold', dest='save_pro_label_fold', default='./ORI_pro_label_fold', help='processed label folder')
|
||||
parser.add_argument('-save_GT_fold', dest='save_GT_label_fold', default='./ORI_GT_label_fold', help='ground truth folder')
|
||||
parser.add_argument('-fold_search_depth', dest='Label_Max_Search_layer', default='1000', type=int, help='Folder Search Depth')
|
||||
parser.add_argument('-pro_suffix_name', dest='pro_append_name', default='_label', help='Pro file suffix')
|
||||
parser.add_argument('-GT_suffix_name', dest='GT_append_name', default='', help='GT file suffix')
|
||||
parser.add_argument('-GT_channel', dest='GT_channel', default='1', type=int, help='GT file channel(1 or 3)')
|
||||
parser.add_argument('-back_gnd_color', dest='back_gnd_color', default='0', type=int, help='Color of "Back ground"(0 or 255)')
|
||||
parser.add_argument('-first_class_color', dest='first_class_color', default='1', type=int, help='Color of "First Class"')
|
||||
parser.add_argument('-pic_type', dest='pic_type', default='png', help='type of picture(Do not add ".")')
|
||||
parser.add_argument('-Max_width', dest='Max_width', default='10000', type=int, help='Max width of picture')
|
||||
parser.add_argument('-Rebuild_from', dest='Rebuild_from', default='label', help='Source to Rebuild Labels(label/pro)')
|
||||
parser.add_argument('-Rebuild_to', dest='Rebuild_to', default='GT', help='Destination of Rebuild Labels(pro/GT)')
|
||||
parser.add_argument('-save_process_pics', dest='save_process_pics', default='False', help='Save the processed pics(e.g.Gray_pics,Color_pics) in generating pro_pics')
|
||||
|
||||
# 解析命令行参数
|
||||
args = parser.parse_args()
|
||||
|
||||
src_label_fold = args.src_label_fold
|
||||
save_pro_label_fold = args.save_pro_label_fold
|
||||
save_GT_label_fold = args.save_GT_label_fold
|
||||
Label_Max_Search_layer = args.Label_Max_Search_layer
|
||||
pro_append_name = args.pro_append_name
|
||||
GT_append_name = args.GT_append_name
|
||||
GT_channel = args.GT_channel
|
||||
back_gnd_color = args.back_gnd_color
|
||||
first_class_color = args.first_class_color
|
||||
pic_type = args.pic_type
|
||||
Max_width = args.Max_width
|
||||
Rebuild_from = args.Rebuild_from
|
||||
Rebuild_to = args.Rebuild_to
|
||||
save_process_pics = args.save_process_pics
|
||||
|
||||
|
||||
try: # 遍历文件深度,最小为1
|
||||
Label_Max_Search_layer=int(Label_Max_Search_layer)
|
||||
except:
|
||||
Label_Max_Search_layer=1000
|
||||
try: # GT标签图片通道数
|
||||
GT_channel=int(GT_channel)
|
||||
except:
|
||||
GT_channel=1
|
||||
try: # 背景颜色(背景选择0或255)
|
||||
back_gnd_color=int(back_gnd_color)
|
||||
except:
|
||||
back_gnd_color=0
|
||||
try: # 第一类上的颜色(如果背景为0,选择1;)
|
||||
first_class_color=int(first_class_color)
|
||||
except:
|
||||
first_class_color=1
|
||||
try: # 最大图片宽度(匹配时候用)
|
||||
Max_width=int(Max_width)
|
||||
except:
|
||||
Max_width=10000
|
||||
if(save_process_pics.lower() == 'false'):
|
||||
save_process_pics = False
|
||||
elif(save_process_pics.lower() == 'true'):
|
||||
save_process_pics = True
|
||||
else:
|
||||
save_process_pics = False
|
||||
|
||||
D = Deal_image(Annotate_CLASSES=Annotate_CLASSES, Annotate_PALETTE=Annotate_PALETTE, src_label_fold=src_label_fold, save_pro_label_fold=save_pro_label_fold, save_GT_label_fold=save_GT_label_fold, GT_channel=GT_channel, pro_append_name=pro_append_name, GT_append_name=GT_append_name, back_gnd_color=back_gnd_color, first_class_color=first_class_color, pic_type=pic_type, Max_width=Max_width, Label_Max_Search_layer=Label_Max_Search_layer, save_process_pics=save_process_pics, bg_PALETTE = bg_PALETTE)
|
||||
# print(D.src_CLASSES_NUM)
|
||||
if Rebuild_from == 'label':
|
||||
# 1.先将所有原始图片转为pro图片
|
||||
D.Conver_ori_label_pic_2_pro_pic_all()
|
||||
pass
|
||||
if Rebuild_to == 'GT':
|
||||
# 2.再将pro图片转为GT图片
|
||||
D.Conver_pro_label_pic_2_GT_pic_all()
|
||||
pass
|
||||
@@ -0,0 +1,169 @@
|
||||
import os
|
||||
import cv2
|
||||
import numpy as np
|
||||
from collections import Counter, defaultdict
|
||||
from PIL import Image
|
||||
from Tool_Classes_And_Palette import Annotate_CLASSES, Annotate_PALETTE, bg_PALETTE
|
||||
|
||||
# ※ 需要修改输入输出路径 ※ #
|
||||
input_dir = './ORI_GT_label_fold'
|
||||
output_dir_1 = 'Data/labels/train'
|
||||
output_dir_2 = 'Data/labels/val'
|
||||
|
||||
# ----------------------------------------------------
|
||||
# ※※※ 1. 修改: 移除背景项并统一格式 ※※※
|
||||
# ----------------------------------------------------
|
||||
# (1) 从 Annotate_CLASSES (元组) 中移除 '背景'
|
||||
if Annotate_CLASSES and Annotate_CLASSES[-1] == '背景':
|
||||
Annotate_CLASSES = Annotate_CLASSES[:-1]
|
||||
# (2) 从 Annotate_PALETTE (列表) 中移除背景颜色 [0,0,0]
|
||||
if Annotate_PALETTE and (Annotate_PALETTE[-1] == [0, 0, 0] or Annotate_PALETTE[-1] == (0, 0, 0)):
|
||||
Annotate_PALETTE.pop()
|
||||
# (3) 确保调色板中的所有颜色都是元组 (Tuple),以便后续查找
|
||||
Annotate_PALETTE = [tuple(color) for color in Annotate_PALETTE]
|
||||
# (4) 确保 bg_PALETTE 是元组,以匹配 Counter 中的键类型
|
||||
bg_PALETTE = tuple(bg_PALETTE)
|
||||
# (5) 检查类别和调色板长度是否一致
|
||||
if len(Annotate_CLASSES) != len(Annotate_PALETTE):
|
||||
print(f"[警告] 移除背景后,类别 ({len(Annotate_CLASSES)}) 和调色板 ({len(Annotate_PALETTE)}) 长度不一致!")
|
||||
else:
|
||||
print(f"--- 成功移除背景项,剩余 {len(Annotate_CLASSES)} 个有效类别。 ---")
|
||||
|
||||
os.makedirs(output_dir_1, exist_ok=True)
|
||||
os.makedirs(output_dir_2, exist_ok=True)
|
||||
|
||||
# 全局统计颜色频率与 class 像素频率
|
||||
global_color_counter = Counter()
|
||||
global_class_counter = Counter() # 将用于统计(ori_label 模式)
|
||||
color_class_counter = defaultdict(int) # (R,G,B) → count
|
||||
color_to_old_class = {} # (R,G,B) → class_id
|
||||
# *** ori_label 模式: 移除了 remap_class_dict ***
|
||||
|
||||
# 自动提取颜色映射(跳过背景)
|
||||
def extract_color_mapping(img_path):
|
||||
img = Image.open(img_path).convert('RGB')
|
||||
pixels = list(img.getdata())
|
||||
counter = Counter(pixels)
|
||||
color_map = {}
|
||||
for color, count in counter.items():
|
||||
global_color_counter[color] += count
|
||||
if color != bg_PALETTE and color[0] == color[1] == color[2]: # 使用元组 bg_PALETTE
|
||||
class_id = color[0] - 1
|
||||
if class_id >= 0:
|
||||
color_map[color] = class_id
|
||||
global_class_counter[class_id] += count # 使用 global_class_counter 统计
|
||||
color_class_counter[color] += count
|
||||
color_to_old_class[color] = class_id
|
||||
return color_map
|
||||
|
||||
# 处理单张图片
|
||||
def process_image(img_path, save_path_list):
|
||||
color_to_class = extract_color_mapping(img_path)
|
||||
if not color_to_class:
|
||||
print(f"[跳过] {os.path.basename(img_path)} 无有效目标")
|
||||
return
|
||||
|
||||
img = cv2.imread(img_path)
|
||||
h, w = img.shape[:2]
|
||||
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
|
||||
lines = []
|
||||
for rgb, old_class_id in color_to_class.items():
|
||||
mask = np.all(img_rgb == rgb, axis=-1).astype(np.uint8) * 255
|
||||
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
for contour in contours:
|
||||
if contour.shape[0] < 3:
|
||||
continue
|
||||
norm_pts = contour.squeeze(1).astype(np.float32)
|
||||
norm_pts[:, 0] /= w
|
||||
norm_pts[:, 1] /= h
|
||||
flat = norm_pts.flatten()
|
||||
|
||||
# *** ori_label 模式: 直接使用 old_class_id ***
|
||||
line = f"{old_class_id} " + " ".join([f"{x:.6f}" for x in flat])
|
||||
lines.append(line)
|
||||
|
||||
if lines:
|
||||
for save_path in save_path_list:
|
||||
with open(save_path, 'w') as f:
|
||||
for line in lines:
|
||||
f.write(line + "\n")
|
||||
print(f"[✔] 转换成功: {os.path.basename(save_path)},共 {len(lines)} 个实例")
|
||||
else:
|
||||
print(f"[⚠] {os.path.basename(img_path)} 没有轮廓")
|
||||
|
||||
# 第一次遍历图像,仅提取颜色信息用于构建 class 映射
|
||||
for fname in os.listdir(input_dir):
|
||||
if fname.lower().endswith('.png'):
|
||||
img_path = os.path.join(input_dir, fname)
|
||||
extract_color_mapping(img_path)
|
||||
|
||||
# *** ori_label 模式: 移除了重映射 (remap) 逻辑块 ***
|
||||
# (保留 class_pixel_count 用于统计)
|
||||
class_pixel_count = {color_to_old_class[c]: count for c, count in color_class_counter.items()}
|
||||
|
||||
# 第二次遍历图像,正式处理并生成标签
|
||||
for fname in os.listdir(input_dir):
|
||||
if fname.lower().endswith('.png'):
|
||||
img_path = os.path.join(input_dir, fname)
|
||||
base_name = os.path.splitext(fname)[0]
|
||||
txt_path_1 = os.path.join(output_dir_1, base_name + '.txt')
|
||||
txt_path_2 = os.path.join(output_dir_2, base_name + '.txt')
|
||||
process_image(img_path, [txt_path_1, txt_path_2])
|
||||
|
||||
# ----------------------------------------------------
|
||||
# ※※※ 2. 修改: 打印详细的颜色统计 ※※※
|
||||
# ----------------------------------------------------
|
||||
print("\n📊 所有图像颜色统计:")
|
||||
|
||||
def get_label_info(class_id):
|
||||
"""辅助函数:根据 class_id 获取标签名和颜色"""
|
||||
if 0 <= class_id < len(Annotate_CLASSES) and 0 <= class_id < len(Annotate_PALETTE):
|
||||
return Annotate_CLASSES[class_id], Annotate_PALETTE[class_id]
|
||||
else:
|
||||
return "未知标签", (255, 255, 255) # 返回一个默认值
|
||||
|
||||
for color, count in global_color_counter.most_common():
|
||||
if color == bg_PALETTE: # 使用 bg_PALETTE 变量
|
||||
print(f"背景颜色 {color} 出现次数: {count}")
|
||||
elif color[0] == color[1] == color[2]:
|
||||
class_id = color[0] - 1
|
||||
label_name, label_color = get_label_info(class_id)
|
||||
print(f"颜色 {color} → class {class_id} (标签: '{label_name}', 颜色: {label_color}),出现次数: {count}")
|
||||
else:
|
||||
# 此情况理论上不应出现,因为 extract_color_mapping 已过滤
|
||||
print(f"[⚠] 非灰阶颜色 {color},出现次数: {count} (此颜色不应被处理)")
|
||||
|
||||
# ----------------------------------------------------
|
||||
# ※※※ 3. 修改: 打印详细的类别统计 (ori_label 模式) ※※※
|
||||
# ----------------------------------------------------
|
||||
print("\n✅ 有效类别统计(按 原 class_id 排序 → 总像素数):")
|
||||
# 按照 old_id (原类别索引) 排序输出
|
||||
sorted_classes = sorted(class_pixel_count.items(), key=lambda item: item[0])
|
||||
|
||||
for old_id, pixel_count in sorted_classes:
|
||||
label_name, label_color = get_label_info(old_id)
|
||||
print(f"class {old_id} (标签: '{label_name}', 颜色: {label_color}): {pixel_count} pixels")
|
||||
|
||||
# ----------------------------------------------------
|
||||
# ※※※ 4. 修改: 额外输出 原 class_id 到颜色的映射 (ori_label 模式) ※※※
|
||||
# ----------------------------------------------------
|
||||
print(f"\n🎨 找到的 原 class_id 与标签颜色 (Annotate_PALETTE) 映射表:")
|
||||
|
||||
# 1. 按照 old_id (0, 1, 2...) 排序并按指定格式打印
|
||||
# 我们使用 class_pixel_count.keys() 来获取所有实际找到的 old_id
|
||||
sorted_old_ids = sorted(class_pixel_count.keys())
|
||||
|
||||
for old_id in sorted_old_ids:
|
||||
if 0 <= old_id < len(Annotate_PALETTE):
|
||||
color_tuple = Annotate_PALETTE[old_id]
|
||||
color_list = list(color_tuple)
|
||||
print(f" {old_id}: {color_list}")
|
||||
else:
|
||||
# 预防性代码
|
||||
print(f" {old_id}: [颜色未在调色板中定义]")
|
||||
# ----------------------------------------------------
|
||||
# ※※※ 修改结束 ※※※
|
||||
# ----------------------------------------------------
|
||||
|
||||
print(f"\n✅ 全部图像处理完毕。标签输出目录:{output_dir_1}、{output_dir_2}")
|
||||
@@ -0,0 +1,204 @@
|
||||
import os
|
||||
import cv2
|
||||
import numpy as np
|
||||
from collections import Counter, defaultdict
|
||||
from PIL import Image
|
||||
from Tool_Classes_And_Palette import Annotate_CLASSES, Annotate_PALETTE, bg_PALETTE
|
||||
|
||||
# ※ 需要修改输入输出路径 ※ #
|
||||
input_dir = 'ORI_GT_label_fold'
|
||||
output_dir_1 = 'Data/labels/train'
|
||||
output_dir_2 = 'Data/labels/val'
|
||||
output_GT_dir_1 = 'Data/labels_GT/train'
|
||||
output_GT_dir_2 = 'Data/labels_GT/val'
|
||||
|
||||
# 1. --- 您提供的类别和调色板 ---
|
||||
# (1) 从 Annotate_CLASSES (元组) 中移除 '背景'
|
||||
if Annotate_CLASSES and Annotate_CLASSES[-1] == '背景':
|
||||
Annotate_CLASSES = Annotate_CLASSES[:-1]
|
||||
# (2) 从 Annotate_PALETTE (列表) 中移除背景颜色 [0,0,0]
|
||||
if Annotate_PALETTE and (Annotate_PALETTE[-1] == [0, 0, 0] or Annotate_PALETTE[-1] == (0, 0, 0)):
|
||||
Annotate_PALETTE.pop()
|
||||
# (3) 确保调色板中的所有颜色都是元组 (Tuple),以便后续查找
|
||||
Annotate_PALETTE = [tuple(color) for color in Annotate_PALETTE]
|
||||
# (4) 确保 bg_PALETTE 是元组,以匹配 Counter 中的键类型
|
||||
bg_PALETTE = tuple(bg_PALETTE)
|
||||
# (5) 检查类别和调色板长度是否一致
|
||||
if len(Annotate_CLASSES) != len(Annotate_PALETTE):
|
||||
print(f"[警告] 移除背景后,类别 ({len(Annotate_CLASSES)}) 和调色板 ({len(Annotate_PALETTE)}) 长度不一致!")
|
||||
else:
|
||||
print(f"--- 成功移除背景项,剩余 {len(Annotate_CLASSES)} 个有效类别。 ---")
|
||||
|
||||
os.makedirs(output_dir_1, exist_ok=True)
|
||||
os.makedirs(output_dir_2, exist_ok=True)
|
||||
os.makedirs(output_GT_dir_1, exist_ok=True)
|
||||
os.makedirs(output_GT_dir_2, exist_ok=True)
|
||||
|
||||
# 全局统计颜色频率与 class 像素频率
|
||||
global_color_counter = Counter()
|
||||
global_class_counter = Counter()
|
||||
color_class_counter = defaultdict(int) # (R,G,B) → count
|
||||
color_to_old_class = {} # (R,G,B) → class_id
|
||||
remap_class_dict = {} # old_class_id → new_class_id
|
||||
|
||||
# 自动提取颜色映射(跳过背景)
|
||||
def extract_color_mapping(img_path):
|
||||
img = Image.open(img_path).convert('RGB')
|
||||
pixels = list(img.getdata())
|
||||
counter = Counter(pixels)
|
||||
color_map = {}
|
||||
for color, count in counter.items():
|
||||
global_color_counter[color] += count
|
||||
if color != (0, 0, 0) and color[0] == color[1] == color[2]:
|
||||
class_id = color[0] - 1
|
||||
if class_id >= 0:
|
||||
color_map[color] = class_id
|
||||
global_class_counter[class_id] += count
|
||||
color_class_counter[color] += count
|
||||
color_to_old_class[color] = class_id
|
||||
return color_map
|
||||
|
||||
# --- 修改:函数签名增加了 gt_save_paths ---
|
||||
def process_image(img_path, txt_save_paths, gt_save_paths):
|
||||
color_to_class = extract_color_mapping(img_path)
|
||||
if not color_to_class:
|
||||
print(f"[跳过] {os.path.basename(img_path)} 无有效目标")
|
||||
return
|
||||
|
||||
img = cv2.imread(img_path)
|
||||
h, w = img.shape[:2]
|
||||
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# --- 新增:创建新的 GT 掩码 (灰度图),0 为背景 ---
|
||||
new_gt_mask = np.zeros((h, w), dtype=np.uint8)
|
||||
lines = []
|
||||
|
||||
for rgb, old_class_id in color_to_class.items():
|
||||
# --- 修改:获取 new_class_id 并填充 new_gt_mask ---
|
||||
new_class_id = remap_class_dict.get(old_class_id, old_class_id)
|
||||
|
||||
# 创建布尔掩码
|
||||
mask_bool = np.all(img_rgb == rgb, axis=-1)
|
||||
|
||||
# --- 新增:在 new_gt_mask 上填充新的 class_id
|
||||
# (使用 new_class_id + 1,因为 0 是背景)
|
||||
new_gt_mask[mask_bool] = new_class_id + 1
|
||||
|
||||
# --- 修改:基于布尔掩码创建 255 掩码用于 findContours ---
|
||||
mask_255 = mask_bool.astype(np.uint8) * 255
|
||||
|
||||
contours, _ = cv2.findContours(mask_255, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
for contour in contours:
|
||||
if contour.shape[0] < 3:
|
||||
continue
|
||||
norm_pts = contour.squeeze(1).astype(np.float32)
|
||||
norm_pts[:, 0] /= w
|
||||
norm_pts[:, 1] /= h
|
||||
flat = norm_pts.flatten()
|
||||
|
||||
# --- 修改:此处 new_class_id 已在循环外层定义 ---
|
||||
line = f"{new_class_id} " + " ".join([f"{x:.6f}" for x in flat])
|
||||
lines.append(line)
|
||||
|
||||
# --- 新增:保存处理后的 GT 掩码图像 ---
|
||||
# 只要存在有效类别 (color_to_class 非空),就保存 GT 掩码
|
||||
for save_path in gt_save_paths:
|
||||
try:
|
||||
# 使用 PIL 保存灰度 PNG 图像
|
||||
Image.fromarray(new_gt_mask).save(save_path)
|
||||
print(f"[✔] GT 掩码保存成功: {os.path.basename(save_path)}")
|
||||
except Exception as e:
|
||||
print(f"[✘] GT 掩码保存失败: {os.path.basename(save_path)} - {e}")
|
||||
|
||||
# 保存 .txt 标签文件 (仅当找到轮廓时)
|
||||
if lines:
|
||||
for save_path in txt_save_paths:
|
||||
with open(save_path, 'w') as f:
|
||||
for line in lines:
|
||||
f.write(line + "\n")
|
||||
print(f"[✔] 转换成功: {os.path.basename(save_path)},共 {len(lines)} 个实例")
|
||||
else:
|
||||
# 即使没有轮廓,GT 掩码也已保存
|
||||
print(f"[⚠] {os.path.basename(img_path)} 没有轮廓 (但 GT 掩码已保存)")
|
||||
|
||||
# 第一次遍历图像,仅提取颜色信息用于构建 class 映射
|
||||
for fname in os.listdir(input_dir):
|
||||
if fname.lower().endswith('.png'):
|
||||
img_path = os.path.join(input_dir, fname)
|
||||
extract_color_mapping(img_path)
|
||||
|
||||
# 构建 class_id 重映射表(按像素数从大到小排序)
|
||||
class_pixel_count = {color_to_old_class[c]: count for c, count in color_class_counter.items()}
|
||||
sorted_classes = sorted(class_pixel_count.items(), key=lambda x: x[1], reverse=True)
|
||||
remap_class_dict = {old_cls: new_idx for new_idx, (old_cls, _) in enumerate(sorted_classes)}
|
||||
|
||||
# 第二次遍历图像,正式处理并生成标签
|
||||
for fname in os.listdir(input_dir):
|
||||
if fname.lower().endswith('.png'):
|
||||
img_path = os.path.join(input_dir, fname)
|
||||
base_name = os.path.splitext(fname)[0]
|
||||
|
||||
txt_path_1 = os.path.join(output_dir_1, base_name + '.txt')
|
||||
txt_path_2 = os.path.join(output_dir_2, base_name + '.txt')
|
||||
|
||||
# --- 新增:定义 GT 掩码输出路径 (保存为 .png) ---
|
||||
gt_path_1 = os.path.join(output_GT_dir_1, base_name + '.png')
|
||||
gt_path_2 = os.path.join(output_GT_dir_2, base_name + '.png')
|
||||
|
||||
process_image(img_path, [txt_path_1, txt_path_2], [gt_path_1, gt_path_2])
|
||||
|
||||
# 打印颜色统计和类别映射
|
||||
print("\n📊 所有图像颜色统计:")
|
||||
|
||||
def get_label_info(class_id):
|
||||
"""辅助函数:根据 class_id 获取标签名和颜色"""
|
||||
if 0 <= class_id < len(Annotate_CLASSES) and 0 <= class_id < len(Annotate_PALETTE):
|
||||
return Annotate_CLASSES[class_id], Annotate_PALETTE[class_id]
|
||||
else:
|
||||
return "未知标签", (255, 255, 255) # 返回一个默认值
|
||||
|
||||
for color, count in global_color_counter.most_common():
|
||||
if color == bg_PALETTE: # 使用 bg_PALETTE 变量
|
||||
print(f"背景颜色 {color} 出现次数: {count}")
|
||||
elif color[0] == color[1] == color[2]:
|
||||
class_id = color[0] - 1
|
||||
label_name, label_color = get_label_info(class_id)
|
||||
print(f"颜色 {color} → class {class_id} (标签: '{label_name}', 颜色: {label_color}),出现次数: {count}")
|
||||
else:
|
||||
# 此情况理论上不应出现,因为 extract_color_mapping 已过滤
|
||||
print(f"[⚠] 非灰阶颜色 {color},出现次数: {count} (此颜色不应被处理)")
|
||||
|
||||
# 打印详细的类别统计
|
||||
print("\n✅ 有效类别统计(原 class_id → 新 class_id → 总像素数):")
|
||||
# 按照 new_id (新类别索引) 排序输出
|
||||
sorted_remap = sorted(remap_class_dict.items(), key=lambda item: item[1])
|
||||
for old_id, new_id in sorted_remap:
|
||||
label_name, label_color = get_label_info(old_id)
|
||||
print(f"class {old_id} (标签: '{label_name}', 颜色: {label_color}) → {new_id}: {class_pixel_count[old_id]} pixels")
|
||||
|
||||
# 4. 额外输出新 class_id 到颜色的映射 ※※※
|
||||
print(f"\n🎨 新 class_id 与标签颜色 (Annotate_PALETTE) 映射表:")
|
||||
# 1. 创建一个 new_id -> color 的映射
|
||||
new_id_to_color = {}
|
||||
for old_id, new_id in remap_class_dict.items():
|
||||
if 0 <= old_id < len(Annotate_PALETTE):
|
||||
# 从 Annotate_PALETTE 获取原始颜色(它是一个元组)
|
||||
color_tuple = Annotate_PALETTE[old_id]
|
||||
# 转换为列表 [R, G, B] 以匹配您要的格式
|
||||
new_id_to_color[new_id] = list(color_tuple)
|
||||
else:
|
||||
# 预防性代码,以防 old_id 超出范围
|
||||
new_id_to_color[new_id] = [-1, -1, -1] # 表示错误/未找到
|
||||
|
||||
# 2. 按照 new_id (0, 1, 2...) 排序并按指定格式打印
|
||||
sorted_new_ids = sorted(new_id_to_color.keys())
|
||||
for new_id in sorted_new_ids:
|
||||
color_list = new_id_to_color[new_id]
|
||||
# 格式化输出: {id}: {color_list}
|
||||
# (注意:颜色列表的格式会自然包含逗号和空格)
|
||||
print(f" {new_id}: {color_list}")
|
||||
|
||||
print(f"\n✅ 全部图像处理完毕。")
|
||||
print(f" 标签 (.txt) 输出目录: {output_dir_1}, {output_dir_2}")
|
||||
print(f" GT 掩码 (.png) 输出目录: {output_GT_dir_1}, {output_GT_dir_2}")
|
||||
@@ -0,0 +1,20 @@
|
||||
# # 胆囊标注
|
||||
# Annotate_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','背景') # 待分类的类
|
||||
# Annotate_PALETTE = [[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[117,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66],[0,0,0]] # 每一类的像素直
|
||||
# # 甲状腺标注
|
||||
# Annotate_CLASSES = ('甲状腺', '针', '双极电凝', '止血海绵', '止血纱布', '喷洒', '标本袋', '肝蒂', '神经', '线', '肝脏', '肝总管', '胆总管', '胆囊管', '引流管', '推结器', '肌肉', '肿瘤', '胆囊', '吸引器', '生物夹', '动脉', '纱布', '乳胶管', '血管阻断夹', '分离钳', '肌肉', '无损伤钳', '电凝', '金属钛夹', '吻合器', '棉球', '脂肪', '超声刀', '钳夹', '静脉', '韧带', '术中超声','背景') # 待分类的类
|
||||
# Annotate_PALETTE = [[255, 148, 81], [134, 124, 118], [155, 62, 0], [181, 227, 14], [160, 15, 95], [90, 120, 41], [112, 113, 150], [133, 102, 140], [168, 162, 252], [0, 157, 142], [255, 91, 0], [72, 0, 255], [0, 155, 33], [255, 0, 0], [0, 255, 0], [202, 202, 200], [254, 141, 179], [254, 161, 0], [255, 234, 0], [255, 0, 255], [0, 160, 233], [117, 0, 0], [255, 255, 255], [197, 83, 181],[255, 172, 159], [85, 111, 181], [29, 32, 136], [52, 184, 178], [166, 24, 232], [0, 254, 254], [136, 162, 196], [146, 175, 236], [155, 132, 0], [240, 16, 116], [66, 115, 82], [42, 8, 66], [181, 85, 105], [138, 251, 213],[0,0,0]] # 每一类的像素直
|
||||
# # 胃癌标注+去雾影像标注
|
||||
Annotate_CLASSES = ('胃', '针', '双极电凝', '止血海绵', '止血纱布', '喷洒', '标本袋', '肝蒂', '小肠', '线', '肝脏', '脾脏', '胆总管', '胆囊管', '引流管', '推结器', '淋巴结', '胰腺', '胆囊', '吸引器', '生物夹', '动脉', '纱布', '乳胶管', '分离钳', '超声刀', '无损伤钳', '电凝', '金属钛夹', '吻合器', '脂肪', '剪刀', '钳夹', '静脉', '韧带','背景') # 待分类的类
|
||||
Annotate_PALETTE = [(237, 35, 85), (134, 124, 118), (155, 62, 0), (187, 227, 14), (160, 15, 95), (90, 120, 41), (112, 113, 150), (133, 102, 140), (110, 255, 166), (0, 157, 142), (255, 91, 0), (72, 0, 255), (0, 155, 33), (255, 0, 0), (0, 255, 0), (202, 202, 200), (201, 255, 74), (245, 161, 0), (255, 234, 0), (255, 0, 255), (0, 160, 233), (117, 0, 0), (255, 255, 255), (197, 83, 181), (85, 111, 181), (29, 32, 136), (52, 184, 178), (167, 24, 233), (0, 255, 255), (136, 162, 196), (155, 132, 0), (240, 16, 116), (66, 115, 82), (42, 8, 66), (181, 85, 105), [0,0,0]] # 每一类的像素直
|
||||
# # 甲状腺标注
|
||||
# Annotate_CLASSES = ('甲状旁腺', '喉返神经', '电凝', '无损伤钳', '超声刀', '分离钳', '纱布', '背景') # 待分类的类
|
||||
# Annotate_PALETTE = [(238, 25, 30), (24, 124, 248), (198, 24, 248), (24, 248, 240), (248, 119, 24), (24, 248, 114), (255,255,255), [0,0,0]] # 每一类的像素值
|
||||
# # 磁器械标注
|
||||
# Annotate_CLASSES = ( '双极电凝', '止血海绵', '止血纱布', '喷洒', '标本袋', '肝脏', '肝总管', '胆总管', '胆囊管', '引流管', '胆囊', '磁器械', '生物夹', '胆囊动脉', '纱布', '分离钳', '剪刀', '无损伤钳','电凝', '金属钛夹', '脂肪', '背景') # 待分类的类
|
||||
# Annotate_PALETTE = [ (155, 62, 0), (181, 227, 14), (160, 15, 95), (90, 120, 41), (112, 113, 150), (255, 91, 0), (72, 0, 255), (0, 155, 33), (255, 0, 0), (0, 255, 0), (255, 234, 0), (255, 0, 255), (0, 160, 233), (117, 0, 0), (255, 255, 255), (85, 111, 181), (29, 32, 136), (52, 184, 178), (167, 24, 233), (0, 255, 255), (155, 132, 0), [0,0,0]] # 每一类的像素值
|
||||
# # 二分类标注
|
||||
# Annotate_CLASSES = ( '特殊部位', '背景') # 待分类的类
|
||||
# Annotate_PALETTE = [ [255,255,255], [0,0,0]] # 每一类的像素值
|
||||
|
||||
bg_PALETTE = [0,0,0] # 背景的RGB
|
||||
@@ -0,0 +1,167 @@
|
||||
import os
|
||||
import argparse
|
||||
from PIL import Image
|
||||
import concurrent.futures
|
||||
|
||||
# 确保使用的是 os.cpu_count() 来自动检测核心数
|
||||
try:
|
||||
DEFAULT_WORKERS = os.cpu_count() or 1
|
||||
except AttributeError:
|
||||
try:
|
||||
import multiprocessing
|
||||
DEFAULT_WORKERS = multiprocessing.cpu_count()
|
||||
except (ImportError, NotImplementedError):
|
||||
DEFAULT_WORKERS = 4
|
||||
|
||||
def _process_single_file(source_path, png_path, delete_source):
|
||||
"""
|
||||
工作函数:处理单个文件的转换、缩放,并保存为PNG。
|
||||
(此函数与上一版完全相同)
|
||||
"""
|
||||
filename = os.path.basename(source_path)
|
||||
png_filename = os.path.basename(png_path)
|
||||
|
||||
try:
|
||||
with Image.open(source_path) as img:
|
||||
width, height = img.size
|
||||
min_side = min(width, height)
|
||||
resize_info = ""
|
||||
|
||||
if min_side > 1080:
|
||||
scale_factor = 1080 / min_side
|
||||
new_width = int(width * scale_factor)
|
||||
new_height = int(height * scale_factor)
|
||||
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
resize_info = f" (已缩放至 {new_width}x{new_height})"
|
||||
|
||||
img.save(png_path, 'PNG')
|
||||
|
||||
if delete_source:
|
||||
os.remove(source_path)
|
||||
return "success_del", filename, f"{png_filename}{resize_info}"
|
||||
else:
|
||||
return "success", filename, f"{png_filename}{resize_info}"
|
||||
|
||||
except Exception as e:
|
||||
return "fail", filename, str(e)
|
||||
|
||||
def process_images(folder_path, delete_source=False, max_workers=DEFAULT_WORKERS):
|
||||
"""
|
||||
批量将指定文件夹 (及其所有子文件夹) 内的 .bmp, .jpg, .jpeg 图像
|
||||
转换为 .png, 并按比例缩放 (最小边<=1080px)。
|
||||
"""
|
||||
|
||||
if not os.path.isdir(folder_path):
|
||||
print(f"错误:文件夹 '{folder_path}' 不存在或不是一个有效的目录。")
|
||||
return
|
||||
|
||||
print(f"--- 开始递归处理文件夹: {folder_path} ---")
|
||||
print(f"--- 使用最多 {max_workers} 个进程并行处理 ---")
|
||||
if delete_source:
|
||||
print("警告:已启用源文件删除模式。")
|
||||
|
||||
# --- 1. 收集所有需要处理的任务 ---
|
||||
tasks = []
|
||||
supported_extensions = ('.bmp', '.jpg', '.jpeg')
|
||||
|
||||
# <--- 更改:从 os.listdir() 切换到 os.walk() 以支持递归
|
||||
print("--- 正在扫描所有子文件夹...")
|
||||
for dirpath, dirnames, filenames in os.walk(folder_path):
|
||||
for filename in filenames:
|
||||
if filename.lower().endswith(supported_extensions):
|
||||
|
||||
# 构建完整的文件路径
|
||||
source_path = os.path.join(dirpath, filename)
|
||||
|
||||
# 构建PNG输出路径 (保持在同一个子文件夹内)
|
||||
base_name = os.path.splitext(filename)[0]
|
||||
png_path = os.path.join(dirpath, base_name + '.png')
|
||||
|
||||
# 避免重复转换
|
||||
if os.path.exists(png_path):
|
||||
# 打印相对路径,使其更清晰
|
||||
relative_path = os.path.relpath(png_path, folder_path)
|
||||
print(f" [跳过] {relative_path} (目标文件已存在)")
|
||||
continue
|
||||
|
||||
tasks.append((source_path, png_path, delete_source))
|
||||
# <--- 更改结束 ---
|
||||
|
||||
if not tasks:
|
||||
print(f"未在文件夹中找到新的 {supported_extensions} 文件。")
|
||||
return
|
||||
|
||||
print(f"--- 发现 {len(tasks)} 个新图像文件,开始转换... ---")
|
||||
|
||||
converted_count = 0
|
||||
failed_count = 0
|
||||
|
||||
# --- 2. 使用 ProcessPoolExecutor 执行任务 (此部分不变) ---
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
future_to_source = {
|
||||
executor.submit(_process_single_file, source, png, delete): source
|
||||
for source, png, delete in tasks
|
||||
}
|
||||
|
||||
# --- 3. 实时获取已完成的结果 (此部分不变) ---
|
||||
for future in concurrent.futures.as_completed(future_to_source):
|
||||
source_path_orig = future_to_source[future]
|
||||
|
||||
try:
|
||||
status, name, result = future.result()
|
||||
|
||||
# 获取相对路径以便于阅读
|
||||
relative_dir = os.path.relpath(os.path.dirname(source_path_orig), folder_path)
|
||||
# 如果是根目录,relative_dir 会是 ".",我们将其替换为空字符串
|
||||
if relative_dir == ".":
|
||||
log_name = name
|
||||
else:
|
||||
log_name = os.path.join(relative_dir, name)
|
||||
|
||||
if status == "success":
|
||||
print(f" [成功] {log_name} -> {result}")
|
||||
converted_count += 1
|
||||
elif status == "success_del":
|
||||
print(f" [成功] {log_name} -> {result} (并已删除源文件)")
|
||||
converted_count += 1
|
||||
elif status == "fail":
|
||||
print(f" [失败] 转换 {log_name} 时出错: {result} (源文件未删除)")
|
||||
failed_count += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f" [严重失败] 处理 {os.path.basename(source_path_orig)} 时进程出错: {e}")
|
||||
failed_count += 1
|
||||
|
||||
print("--- 处理完毕 ---")
|
||||
if converted_count > 0:
|
||||
print(f"成功转换 {converted_count} 个文件。")
|
||||
if failed_count > 0:
|
||||
print(f"失败 {failed_count} 个文件。")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="将指定文件夹(及其所有子文件夹)中的所有 .bmp, .jpg, .jpeg 文件批量转换为 .png 文件 (并行加速),并按比例缩放 (最小边<=1080px)。"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"folder",
|
||||
type=str,
|
||||
help="包含 .bmp, .jpg, .jpeg 图像的 (根) 目标文件夹路径 (例如 ./ABC)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-d", "--delete-source",
|
||||
action="store_true",
|
||||
help="在成功转换为 .png 后,删除原始的 .bmp/.jpg/.jpeg 文件。"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-w", "--workers",
|
||||
type=int,
|
||||
default=DEFAULT_WORKERS,
|
||||
help=f"指定用于转换的工作进程数 (默认: {DEFAULT_WORKERS}, 即本机CPU核心数)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
process_images(args.folder, args.delete_source, args.workers)
|
||||
180
Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_deal_labels.py
Normal file
180
Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_deal_labels.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import cv2
|
||||
import random
|
||||
import numpy as np
|
||||
from scipy.ndimage import label, distance_transform_edt
|
||||
|
||||
def skeletonize(image):
|
||||
"""骨架化函数,确保线条连通性并缩减为1像素宽"""
|
||||
skeleton = np.zeros_like(image)
|
||||
temp_image = np.copy(image)
|
||||
|
||||
while True:
|
||||
eroded = cv2.erode(temp_image, None) # 腐蚀操作
|
||||
temp_dilate = cv2.dilate(eroded, None) # 膨胀操作
|
||||
temp = cv2.subtract(temp_image, temp_dilate) # 提取边缘
|
||||
skeleton = cv2.bitwise_or(skeleton, temp) # 将边缘加入骨架
|
||||
temp_image = np.copy(eroded)
|
||||
if cv2.countNonZero(temp_image) == 0:
|
||||
break
|
||||
return skeleton
|
||||
|
||||
# 1. *** 边缘检测并膨胀 ***
|
||||
def edge_detection(image):
|
||||
"""对图像的各个通道进行边缘检测并进行膨胀处理"""
|
||||
b_channel, g_channel, r_channel = cv2.split(image)
|
||||
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
edges_b = cv2.Canny(b_channel, 100, 200)
|
||||
edges_g = cv2.Canny(g_channel, 100, 200)
|
||||
edges_r = cv2.Canny(r_channel, 100, 200)
|
||||
edges_gray = cv2.Canny(gray_image, 100, 200)
|
||||
|
||||
# 合并所有边缘检测结果
|
||||
edges = cv2.bitwise_or(edges_b, edges_g)
|
||||
edges = cv2.bitwise_or(edges, edges_r)
|
||||
edges = cv2.bitwise_or(edges, edges_gray)
|
||||
|
||||
# 创建膨胀核并进行膨胀操作
|
||||
kernel = np.ones((3, 3), np.uint8)
|
||||
dilated_image = cv2.dilate(edges, kernel, iterations=1)
|
||||
|
||||
return dilated_image
|
||||
|
||||
# 2. ** 检测连通区域 ***
|
||||
def detect_connected_regions(dilated_image):
|
||||
"""检测图像中的连通区域并过滤掉小区域"""
|
||||
_, binary = cv2.threshold(dilated_image, 1, 255, cv2.THRESH_BINARY_INV)
|
||||
binary[binary > 0] = 1 # 转换为二值图像
|
||||
|
||||
# 标记连通区域
|
||||
structure = np.ones((3, 3), dtype=int)
|
||||
labeled_array, num_features = label(binary, structure=structure)
|
||||
|
||||
# 清除掉小于100像素的区域
|
||||
filtered_labeled_array = np.copy(labeled_array)
|
||||
for label_num in range(1, num_features + 1):
|
||||
area = np.sum(labeled_array == label_num)
|
||||
if area < 100:
|
||||
filtered_labeled_array[filtered_labeled_array == label_num] = 0
|
||||
|
||||
# 重新标记过滤后的连通区域
|
||||
filtered_labeled_array, num_features = label(filtered_labeled_array, structure=structure)
|
||||
return filtered_labeled_array, num_features # 返回过滤后的labeled_array和未过滤的labeled_array(用于寻找颜色)
|
||||
|
||||
# 3. *** 分水岭填充白色区域 ***
|
||||
def fill_white_regions(filtered_labeled_array):
|
||||
"""使用分水岭算法填充白色区域"""
|
||||
# 准备三通道图像作为分水岭算法的输入
|
||||
color_image = np.zeros((filtered_labeled_array.shape[0], filtered_labeled_array.shape[1], 3), dtype=np.uint8)
|
||||
|
||||
# 将过滤后的 labeled_array 转化为 32 位整型,作为分水岭的 markers
|
||||
markers = np.copy(filtered_labeled_array).astype(np.int32)
|
||||
markers[markers == 0] = -1 # 背景标记为 -1
|
||||
|
||||
# 执行分水岭算法
|
||||
cv2.watershed(color_image, markers)
|
||||
|
||||
# 更新分水岭结果
|
||||
filled_labeled_array = markers.astype(int)
|
||||
filled_labeled_array[filled_labeled_array == -1] = 0
|
||||
|
||||
# 使用距离变换,计算边缘像素(0)最近的非零值
|
||||
non_zero_mask = filled_labeled_array != 0
|
||||
distance, nearest_indices = distance_transform_edt(non_zero_mask == 0, return_indices=True)
|
||||
nearest_values = filled_labeled_array[tuple(nearest_indices)]
|
||||
filled_labeled_array[filled_labeled_array == 0] = nearest_values[filled_labeled_array == 0]
|
||||
|
||||
return filled_labeled_array
|
||||
|
||||
# 4. *** 对连通区域上色(使用“filtered_labeled_array”作为颜色判断,给“filled_labeled_array”上色) ***
|
||||
def color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, Annotate_PALETTE):
|
||||
"""根据原始图像的颜色和注解调色板给连通区域上色"""
|
||||
|
||||
# 初始化一个三通道的彩色图像
|
||||
colored_image = np.zeros((*filled_labeled_array.shape, 3), dtype=np.uint8)
|
||||
|
||||
# 遍历filtered_labeled_array中的每个标签
|
||||
unique_labels = np.unique(filtered_labeled_array)
|
||||
|
||||
for label_num in unique_labels:
|
||||
if label_num == 0:
|
||||
continue # 跳过背景标签
|
||||
|
||||
# 找到filtered_labeled_array中等于当前标签的区域
|
||||
mask_filtered = (filtered_labeled_array == label_num)
|
||||
|
||||
# 获取ori_labeled_image中对应区域的RGB值
|
||||
region_rgb_values = ori_labeled_image[mask_filtered]
|
||||
|
||||
if len(region_rgb_values) == 0:
|
||||
continue
|
||||
|
||||
# 计算区域的RGB平均值
|
||||
average_rgb = np.mean(region_rgb_values, axis=0)
|
||||
|
||||
# 找到Annotate_PALETTE中与average_rgb最接近的颜色
|
||||
closest_palette_color = find_closest_palette_color(average_rgb, Annotate_PALETTE)
|
||||
|
||||
# 将该颜色赋给filled_labeled_array对应区域的元素
|
||||
mask_filled = (filled_labeled_array == label_num)
|
||||
colored_image[mask_filled] = closest_palette_color
|
||||
|
||||
return colored_image
|
||||
|
||||
# 寻找最近邻颜色
|
||||
def find_closest_palette_color(average_rgb, Annotate_PALETTE):
|
||||
"""根据平均RGB值找到Annotate_PALETTE中最接近的颜色"""
|
||||
Annotate_PALETTE = [[color[2], color[1], color[0]] for color in Annotate_PALETTE]
|
||||
|
||||
average_rgb = np.array(average_rgb)
|
||||
min_distance = float('inf')
|
||||
closest_color = None
|
||||
|
||||
# 遍历调色板,计算每个颜色与平均RGB的欧几里得距离
|
||||
for palette_color in Annotate_PALETTE:
|
||||
palette_color = np.array(palette_color)
|
||||
distance = np.linalg.norm(average_rgb - palette_color) # 欧几里得距离
|
||||
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
closest_color = palette_color
|
||||
|
||||
return closest_color
|
||||
|
||||
# 5. 对Array区域上色(4的简化版)
|
||||
def Tool_color_connected_array(Array):
|
||||
colored_image = np.zeros((*Array.shape, 3), dtype=np.uint8)
|
||||
for label_num in range(1, np.max(Array) + 1):
|
||||
color = [np.random.randint(0, 254) for _ in range(3)]
|
||||
colored_image[Array == label_num] = color
|
||||
return colored_image
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""超参数"""
|
||||
image_path = './2023_02_03_09_13_48.00_08_04_21.Still085.png'
|
||||
|
||||
"""主函数,处理图像并保存结果"""
|
||||
# 读取图像
|
||||
image = cv2.imread(image_path)
|
||||
|
||||
# 1. 边缘检测并膨胀
|
||||
dilated_image = edge_detection(image)
|
||||
cv2.imwrite('./1_1_range_image.png', dilated_image)
|
||||
|
||||
# 2. 检测连通区域
|
||||
filtered_labeled_array, _ = detect_connected_regions(dilated_image)
|
||||
colored_image_filtered = Tool_color_connected_array(filtered_labeled_array)
|
||||
cv2.imwrite('./2_colored_image_filtered.png', colored_image_filtered)
|
||||
|
||||
# 3. 分水岭填充白色区域
|
||||
filled_labeled_array = fill_white_regions(filtered_labeled_array)
|
||||
colored_image_filled = Tool_color_connected_array(filled_labeled_array)
|
||||
cv2.imwrite('./3_colored_image_filled.png', colored_image_filled)
|
||||
|
||||
# 4. 对连通区域上色
|
||||
ori_labeled_image = image
|
||||
colored_image_final = color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, Annotate_PALETTE)
|
||||
cv2.imwrite('./4_color_image_Final.png', colored_image_final)
|
||||
|
||||
|
||||
print("处理后的图片已保存。")
|
||||
190
Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_resize_pics.py
Normal file
190
Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_resize_pics.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import argparse
|
||||
from PIL import Image
|
||||
import concurrent.futures
|
||||
import time
|
||||
|
||||
# --- 1. 复制自您脚本中的 CPU 核心数检测逻辑 ---
|
||||
try:
|
||||
DEFAULT_WORKERS = os.cpu_count() or 1
|
||||
except AttributeError:
|
||||
try:
|
||||
import multiprocessing
|
||||
DEFAULT_WORKERS = multiprocessing.cpu_count()
|
||||
except (ImportError, NotImplementedError):
|
||||
DEFAULT_WORKERS = 4
|
||||
|
||||
# --- 2. 定义支持的图片格式 ---
|
||||
SUPPORTED_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.bmp', '.webp', '.tiff', '.tif')
|
||||
|
||||
# --- 3. 确定Pillow的重采样过滤器 ---
|
||||
try:
|
||||
RESAMPLE_FILTER = Image.Resampling.LANCZOS
|
||||
except AttributeError:
|
||||
RESAMPLE_FILTER = Image.LANCZOS
|
||||
|
||||
|
||||
def _process_single_image(image_path, target_min_side):
|
||||
"""
|
||||
工作函数:处理单个文件的缩放。(此函数与上一版完全相同)
|
||||
|
||||
:param image_path: 完整图片路径
|
||||
:param target_min_side: 目标最小边长 (例如 1080)
|
||||
:return: (状态, 原始文件名, 结果/错误信息)
|
||||
"""
|
||||
filename = os.path.basename(image_path)
|
||||
|
||||
try:
|
||||
with Image.open(image_path) as img:
|
||||
img_info = img.info.copy()
|
||||
width, height = img.size
|
||||
min_side = min(width, height)
|
||||
|
||||
# --- 核心逻辑:检查是否需要缩放 ---
|
||||
if min_side <= target_min_side:
|
||||
# 返回相对路径以便于阅读
|
||||
return "skipped", image_path, f"最小边 ({min_side}px) 已 <= {target_min_side}px"
|
||||
|
||||
# --- 计算新尺寸 ---
|
||||
scale_ratio = target_min_side / min_side
|
||||
new_width = int(width * scale_ratio)
|
||||
new_height = int(height * scale_ratio)
|
||||
|
||||
# --- 执行缩放 ---
|
||||
resized_img = img.resize((new_width, new_height), RESAMPLE_FILTER)
|
||||
|
||||
# --- 覆盖保存 ---
|
||||
resized_img.save(image_path, **img_info)
|
||||
|
||||
# 返回相对路径以便于阅读
|
||||
return "success", image_path, f"从 {width}x{height} -> {new_width}x{new_height}"
|
||||
|
||||
except Exception as e:
|
||||
# 返回相对路径以便于阅读
|
||||
return "fail", image_path, str(e)
|
||||
|
||||
def resize_images_in_folder(folder_path, target_min_side=1080, max_workers=DEFAULT_WORKERS):
|
||||
"""
|
||||
*** [已更新] ***
|
||||
批量将文件夹及其所有子文件夹中最小边 > target_min_side 的图片等比例缩小。
|
||||
|
||||
:param folder_path: 目标根文件夹路径
|
||||
:param target_min_side: 目标最小边长
|
||||
:param max_workers: 使用的进程数
|
||||
"""
|
||||
|
||||
if not os.path.isdir(folder_path):
|
||||
print(f"错误:文件夹 '{folder_path}' 不存在或不是一个有效的目录。")
|
||||
return
|
||||
|
||||
print(f"--- 开始 **递归** 处理文件夹: {folder_path} ---")
|
||||
print(f"--- 目标最小边: {target_min_side}px ---")
|
||||
print(f"--- 使用最多 {max_workers} 个进程并行处理 ---")
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# --- 1. 收集所有需要处理的任务 (*** [核心修改] ***) ---
|
||||
tasks = []
|
||||
print("--- 正在递归扫描所有子文件夹... ---")
|
||||
|
||||
# 使用 os.walk 递归遍历
|
||||
for dirpath, dirnames, filenames in os.walk(folder_path):
|
||||
for filename in filenames:
|
||||
# 检查文件扩展名是否在支持的列表中
|
||||
if filename.lower().endswith(SUPPORTED_EXTENSIONS):
|
||||
# 构造完整的文件路径
|
||||
image_path = os.path.join(dirpath, filename)
|
||||
tasks.append((image_path, target_min_side))
|
||||
|
||||
if not tasks:
|
||||
print(f"未在 {folder_path} 及其子文件夹中找到支持的图片文件。")
|
||||
return
|
||||
|
||||
print(f"--- 发现 {len(tasks)} 个图片文件,开始处理... ---")
|
||||
|
||||
resized_count = 0
|
||||
skipped_count = 0
|
||||
failed_count = 0
|
||||
|
||||
# 转换为相对路径,使输出更简洁
|
||||
base_folder_path = os.path.abspath(folder_path)
|
||||
|
||||
# --- 2. 使用 ProcessPoolExecutor 执行任务 (与上一版相同) ---
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
future_to_path = {
|
||||
executor.submit(_process_single_image, path, size): path
|
||||
for path, size in tasks
|
||||
}
|
||||
|
||||
# --- 3. 实时获取已完成的结果 (与上一版相同) ---
|
||||
for future in concurrent.futures.as_completed(future_to_path):
|
||||
try:
|
||||
# status, path_or_name, result
|
||||
status, image_path, result = future.result()
|
||||
|
||||
# 转换为相对路径
|
||||
try:
|
||||
display_path = os.path.relpath(image_path, base_folder_path)
|
||||
except ValueError:
|
||||
display_path = image_path # 如果不在同一驱动器(Windows),则显示完整路径
|
||||
|
||||
if status == "success":
|
||||
print(f" [成功] {display_path}: {result}")
|
||||
resized_count += 1
|
||||
elif status == "skipped":
|
||||
print(f" [跳过] {display_path}: {result}")
|
||||
skipped_count += 1
|
||||
elif status == "fail":
|
||||
print(f" [失败] {display_path}: {result}")
|
||||
failed_count += 1
|
||||
|
||||
except Exception as e:
|
||||
orig_path = future_to_path[future]
|
||||
print(f" [严重失败] 处理 {orig_path} 时进程出错: {e}")
|
||||
failed_count += 1
|
||||
|
||||
end_time = time.time()
|
||||
print("--- 处理完毕 ---")
|
||||
if resized_count > 0:
|
||||
print(f"成功缩放 {resized_count} 个文件。")
|
||||
if skipped_count > 0:
|
||||
print(f"跳过 {skipped_count} 个文件 (无需缩放)。")
|
||||
if failed_count > 0:
|
||||
print(f"失败 {failed_count} 个文件。")
|
||||
print(f"总耗时: {end_time - start_time:.2f} 秒。")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="*** [已更新] *** 批量 **递归** 缩放文件夹及其子文件夹中的图片,使最小边不超过指定值。"
|
||||
)
|
||||
|
||||
# 位置参数:文件夹路径 (必需)
|
||||
parser.add_argument(
|
||||
"folder",
|
||||
type=str,
|
||||
help="包含图片的 **根** 文件夹路径 (例如 ./MyImages),将递归处理所有子文件夹。"
|
||||
)
|
||||
|
||||
# 选项参数:目标尺寸 (可选)
|
||||
parser.add_argument(
|
||||
"-s", "--size",
|
||||
type=int,
|
||||
default=1080,
|
||||
help="指定目标最小边长 (默认: 1080)。如果图片最小边已小于此值,则跳过。"
|
||||
)
|
||||
|
||||
# 选项参数:控制进程数 (可选)
|
||||
parser.add_argument(
|
||||
"-w", "--workers",
|
||||
type=int,
|
||||
default=DEFAULT_WORKERS,
|
||||
help=f"指定用于转换的工作进程数 (默认: {DEFAULT_WORKERS}, 即本机CPU核心数)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 将文件夹路径、"目标尺寸" 和 "工作进程数" 传递给函数
|
||||
resize_images_in_folder(args.folder, args.size, args.workers)
|
||||
69
Seg_All_In_One_YoloModel/Yolo数据集构建/Yolo数据集构建_使用手册
Normal file
69
Seg_All_In_One_YoloModel/Yolo数据集构建/Yolo数据集构建_使用手册
Normal file
@@ -0,0 +1,69 @@
|
||||
#### 0. 准备工作 ####
|
||||
# 清理旧文件
|
||||
rm -r ./Label/* ./ORI/*
|
||||
rm -r ./result_stack_*透明度 ./Data
|
||||
rm -r ./ORI_GT_label_fold ./ORI_pro_label_fold ./__pycache__
|
||||
conda activate SMP
|
||||
|
||||
# A. 图像移动
|
||||
cp 磁辅助分割图像-有效图/* Label/ # TODO TODO 将标注后图片放入Label中 # 保证Label中图片与ORI中图片一一对应,且命名相同
|
||||
cp 磁辅助分割-原图/* ORI/ # TODO TODO 将原始图片放入ORI中
|
||||
# B.1 修改类别颜色
|
||||
vim Tool_Classes_And_Palette.py # 修改 Annotate_CLASSES、Annotate_PALETTE 以匹配标注时的类别与颜色
|
||||
# B.2 将bmp、jpg图片转为png
|
||||
python Tool_convert_bmp_jpg_to_png.py ./Label --delete-source
|
||||
python Tool_convert_bmp_jpg_to_png.py ./ORI --delete-source
|
||||
# B.3 将图片转为最大边限制为1080
|
||||
python Tool_resize_pics.py ./Label # -s 1080
|
||||
python Tool_resize_pics.py ./ORI # -s 1080
|
||||
# C. 检测图片是否匹配
|
||||
python 0_1_check_picture_pair.py # -i "./ORI" -l "./Label" -p "" -s ""
|
||||
# python 0_1_check_picture_pair.py # -i "../../DataSet_Public/6_CWK_2_cfz/images/train" -l "../../DataSet_Public/6_CWK_2_cfz/labels_GT/train" -p "" -s ""
|
||||
# D. 生成堆叠图片(可视化标签效果)
|
||||
bash 0_2_TOOL_stack_pics.sh -i "./ORI" -l ./Label -r ./result_stack_0.3透明度 -a 0.3 -p "" -s "_label"
|
||||
# E. ※下载图片,查看匹配的是否有问题※
|
||||
|
||||
#### 1. 批量化生成训练、测试集图片 ####
|
||||
# A. 将图片转为GT图片
|
||||
python 1_deal_labels.py -src_fold ./Label
|
||||
# B. 新建数据最终存储文件夹
|
||||
rm -r ./Data
|
||||
mkdir -p Data/images/train Data/images/val
|
||||
cp ORI/* Data/images/train/
|
||||
cp ORI/* Data/images/val/ # 或根据需要分配训练集、验证集
|
||||
mkdir -p Data/labels/train Data/labels/val
|
||||
mkdir -p Data/labels_GT/train Data/labels_GT/val
|
||||
# C. 生成labels 、 labels_GT图片到 Data/labels_GT/train 中
|
||||
# Way 1:※推荐※(将类别压缩)
|
||||
python 2_Check_and_Gen_Txt_Label_sort_label.py
|
||||
# Way 2:(使用原始类别)
|
||||
# python 2_Check_and_Gen_Txt_Label_ori_label.py # Way 2
|
||||
# cp ORI_GT_label_fold/* Data/labels_GT/train/ # Way 2
|
||||
# cp ORI_GT_label_fold/* Data/labels_GT/val/ # Way 2
|
||||
|
||||
#### 2. 纳入到Yolo训练体系 ####
|
||||
# A. 移动数据
|
||||
cp -r ./Data ../../DataSet_Public/8_Haze_Baidu_Plus # TODO TODO 将 Data 文件夹改名后 放入 ../../DataSet_Public 下
|
||||
# B. 设置输出颜色参考
|
||||
将 程序输出的信息存入 ../../Seg_All_In_One_YoloModel/dataset.yaml 的 color中作为最终输出颜色的参考
|
||||
# C. 根据 Seg_All_In_One_YoloModel 中的使用手册新增数据集、进行配置、训练
|
||||
修改 dataset.yaml 中 训练数据集、修改 yolo_config.py 中 EPOCHS、PATIENCE
|
||||
|
||||
############################## 进行训练推理(整体流程) ############################################
|
||||
# 1. 批量化训练
|
||||
conda activate SMP
|
||||
cd ~/Desktop/Seg/Seg_All_In_One_YoloModel
|
||||
bash yolo_train.sh
|
||||
# 2. 复制最优模型到预测文件夹
|
||||
bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "best.pt" && bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "epoch100.pt" && bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "epoch50.pt" && bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "epoch150.pt"
|
||||
# 3. 批量化预测+热度图可视化
|
||||
bash yolo_predict.sh --conf 0.2 --pt_name "epoch100.pt" && bash yolo_predict.sh --conf 0.2 && bash yolo_predict.sh --conf 0.2 --pt_name "epoch50.pt" && bash yolo_predict.sh --conf 0.2 --pt_name "epoch150.pt"
|
||||
bash ./yolo_predict.sh --pt_name "best.pt" --heatmap_method "All" # && bash ./yolo_predict.sh --pt_name "epoch100.pt" --heatmap_method "All"
|
||||
# 4. 横向对比结果
|
||||
python yolo_predict_V2_compare_all.py
|
||||
# 5. 打包预测结果(不包含*.pt模型文件)
|
||||
cd /home/wkmgc/Desktop/Seg/BestMode_Predict_Results_DataSet_Public/
|
||||
zip -r /home/wkmgc/Desktop/8_Haze_Baidu_Plus-Yolo.zip 8_Haze_Baidu_Plus*-Yolo -x "*.pt" # TODO TODO
|
||||
# 6. 打包训练结果(只有.png、.jpg、.csv文件)
|
||||
cd /home/wkmgc/Desktop/Seg/Hardisk/
|
||||
zip -r /home/wkmgc/Desktop/8_Haze_Baidu_Plus-Yolo_train.zip 8_Haze_Baidu_Plus-Yolo -i \*.png \*.jpg \*.csv # TODO TODO
|
||||
Reference in New Issue
Block a user