整合去雾网页工具
This commit is contained in:
63
※程序-后处理汇总/2_Adjust_V1_HSV中SV直方图调整_很一般.py
Normal file
63
※程序-后处理汇总/2_Adjust_V1_HSV中SV直方图调整_很一般.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from skimage import exposure
|
||||
from matplotlib import colors
|
||||
|
||||
def match_image_hsv(source_path, reference_path, output_path):
|
||||
# 1. 读取图片并归一化到 0-1 (matplotlib 的 hsv 转换需要 0-1)
|
||||
src_rgb = np.array(Image.open(source_path)) / 255.0
|
||||
ref_rgb = np.array(Image.open(reference_path)) / 255.0
|
||||
|
||||
# 2. 将图片从 RGB 转换为 HSV
|
||||
src_hsv = colors.rgb_to_hsv(src_rgb)
|
||||
ref_hsv = colors.rgb_to_hsv(ref_rgb)
|
||||
|
||||
# 3. 分离 HSV 通道
|
||||
# src_h: 色调, src_s: 饱和度, src_v: 亮度
|
||||
s_h, s_s, s_v = src_hsv[:,:,0], src_hsv[:,:,1], src_hsv[:,:,2]
|
||||
r_h, r_s, r_v = ref_hsv[:,:,0], ref_hsv[:,:,1], ref_hsv[:,:,2]
|
||||
|
||||
# 4. 对 V (亮度) 和 S (饱和度) 通道进行直方图匹配
|
||||
# 我们使用参考图的 V 和 S 分布来调整源图
|
||||
matched_h = exposure.match_histograms(s_h, r_h)
|
||||
matched_v = exposure.match_histograms(s_v, r_v)
|
||||
matched_s = exposure.match_histograms(s_s, r_s)
|
||||
|
||||
# 5. 合并通道
|
||||
# 使用原始的 H (色调),加上匹配后的 S 和 V
|
||||
# V1 不调整H版本
|
||||
matched_hsv = np.stack([s_h, matched_s, matched_v], axis=2)
|
||||
# V2 调整H版本
|
||||
# matched_hsv = np.stack([matched_h, matched_s, matched_v], axis=2)
|
||||
|
||||
# 6. 转换回 RGB 并保存
|
||||
# 转换回 RGB 后需要 clip 到 0-1 范围,防止数值溢出
|
||||
matched_rgb = np.clip(colors.hsv_to_rgb(matched_hsv), 0, 1)
|
||||
# 将 0-1 转换回 0-255 的整数并保存
|
||||
result_image = Image.fromarray((matched_rgb * 255).astype(np.uint8))
|
||||
result_image.save(output_path)
|
||||
print(f"HSV 处理完成,图片已保存至: {output_path}")
|
||||
|
||||
def match_image_appearance(source_path, reference_path, output_path):
|
||||
# 1. 读取图片
|
||||
# source: 需要调整的图片 (第一张, 偏暗)
|
||||
# reference: 目标风格图片 (第二张, 正常)
|
||||
src = np.array(Image.open(source_path))
|
||||
ref = np.array(Image.open(reference_path))
|
||||
|
||||
# 2. 进行直方图匹配
|
||||
# channel_axis=-1 表示对 RGB 每个通道分别进行匹配
|
||||
matched = exposure.match_histograms(src, ref, channel_axis=-1)
|
||||
|
||||
# 3. 保存结果
|
||||
result_image = Image.fromarray(matched.astype(np.uint8))
|
||||
result_image.save(output_path)
|
||||
print(f"处理完成,图片已保存至: {output_path}")
|
||||
|
||||
# 使用示例
|
||||
source_file = "./去雾图像-北航合作/2025-07-02_084220_VID002.mp4_20251027_001308.661.png"
|
||||
reference_file = "./去雾图像-北航合作-Result_Baidu/2025-07-02_084220_VID002.mp4_20251027_001308.661.png"
|
||||
# V1
|
||||
# match_image_appearance(source_file, reference_file, "adjusted_image_rgb.png")
|
||||
# V2
|
||||
match_image_hsv(source_file, reference_file, "adjusted_image_hsv.png")
|
||||
156
※程序-后处理汇总/2_Adjust_V2_SV自动调整_※Best.py
Normal file
156
※程序-后处理汇总/2_Adjust_V2_SV自动调整_※Best.py
Normal file
@@ -0,0 +1,156 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from skimage import color
|
||||
from scipy.optimize import minimize
|
||||
import os
|
||||
import time
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
|
||||
# ================= 配置区域 =================
|
||||
|
||||
max_workers = 8 # 【并行】 并行处理的进程数 (建议设为 CPU 核心数,如 4, 8, 16)
|
||||
# 图片后缀
|
||||
prefix = "_AOD-Net" # "_10_0.2_result" # "_result"
|
||||
# 输入文件夹 (带 prefix 后缀的图片)
|
||||
src_dir = "AOD-Net"
|
||||
# 参考文件夹 (GT/Ground Truth,无 prefix 后缀)
|
||||
ref_dir = "去雾图像-北航合作-雾图"
|
||||
# 输出文件夹
|
||||
out_dir = "AOD-Net+后处理"
|
||||
|
||||
# ===========================================
|
||||
|
||||
# 尝试导入 tqdm,如果没安装则定义一个简单的占位符
|
||||
try:
|
||||
from tqdm import tqdm
|
||||
except ImportError:
|
||||
def tqdm(iterable, **kwargs):
|
||||
return iterable
|
||||
|
||||
def process_single_image(filename, src_folder, ref_folder, output_folder, prefix=prefix):
|
||||
"""
|
||||
处理单张图片的函数,用于并行调用
|
||||
"""
|
||||
source_path = os.path.join(src_folder, filename)
|
||||
|
||||
try:
|
||||
# --- 寻找对应的参考图 ---
|
||||
# 逻辑:去除文件名后缀 "prefix"
|
||||
name_no_ext, ext = os.path.splitext(filename)
|
||||
|
||||
if name_no_ext.endswith(prefix):
|
||||
ref_name_no_ext = name_no_ext[:-len(prefix)] # 去掉最后n个字符 (prefix)
|
||||
else:
|
||||
ref_name_no_ext = name_no_ext
|
||||
|
||||
ref_filename = ref_name_no_ext + ext
|
||||
ref_path = os.path.join(ref_folder, ref_filename)
|
||||
|
||||
# 检查参考图是否存在
|
||||
if not os.path.exists(ref_path):
|
||||
return f"[跳过] 找不到参考图: {ref_filename} (对应: {filename})"
|
||||
|
||||
# --- 读取图片并归一化 ---
|
||||
img_src_pil = Image.open(source_path).convert('RGB')
|
||||
img_src = np.array(img_src_pil) / 255.0
|
||||
|
||||
img_ref_pil = Image.open(ref_path).convert('RGB')
|
||||
|
||||
# 确保参考图尺寸和源图一致
|
||||
if img_src_pil.size != img_ref_pil.size:
|
||||
img_ref_pil = img_ref_pil.resize(img_src_pil.size, Image.BILINEAR)
|
||||
|
||||
img_ref = np.array(img_ref_pil) / 255.0
|
||||
|
||||
# --- 转换到 HSV ---
|
||||
hsv_src = color.rgb2hsv(img_src)
|
||||
hsv_ref = color.rgb2hsv(img_ref)
|
||||
|
||||
# --- 定义损失函数 ---
|
||||
# 注意:在多进程中,loss_function 必须定义在 worker 内部才能访问到 hsv_src/ref
|
||||
def loss_function(params):
|
||||
ks, kv = params
|
||||
adj_s = np.clip(hsv_src[:,:,1] * ks, 0, 1)
|
||||
adj_v = np.clip(hsv_src[:,:,2] * kv, 0, 1)
|
||||
loss_s = np.mean((adj_s - hsv_ref[:,:,1])**2)
|
||||
loss_v = np.mean((adj_v - hsv_ref[:,:,2])**2)
|
||||
return loss_s + loss_v
|
||||
|
||||
# --- 开始优化 ---
|
||||
res = minimize(loss_function, [1.0, 1.0], method='Nelder-Mead', tol=1e-4)
|
||||
best_s, best_v = res.x
|
||||
|
||||
s_percent = int(best_s * 100)
|
||||
v_percent = int(best_v * 100)
|
||||
|
||||
# --- 应用最佳参数 ---
|
||||
hsv_new = hsv_src.copy()
|
||||
hsv_new[:, :, 1] = np.clip(hsv_new[:, :, 1] * best_s, 0, 1)
|
||||
hsv_new[:, :, 2] = np.clip(hsv_new[:, :, 2] * best_v, 0, 1)
|
||||
|
||||
# --- 转回 RGB 并保存 ---
|
||||
img_result_rgb = color.hsv2rgb(hsv_new)
|
||||
img_save = Image.fromarray((img_result_rgb * 255).astype(np.uint8))
|
||||
|
||||
new_filename = f"{name_no_ext}_S_{s_percent}_V_{v_percent}{ext}"
|
||||
save_path = os.path.join(output_folder, new_filename)
|
||||
|
||||
img_save.save(save_path)
|
||||
|
||||
return f"OK: {filename} -> S={s_percent}%, V={v_percent}%"
|
||||
|
||||
except Exception as e:
|
||||
return f"[错误] 处理文件 {filename} 时出错: {str(e)}"
|
||||
|
||||
|
||||
def calculate_and_process_batch_parallel(src_folder, ref_folder, output_folder, max_workers=None):
|
||||
# 1. 确保输出目录存在
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
print(f"已创建输出目录: {output_folder}")
|
||||
|
||||
# 2. 获取源文件夹内所有图片文件
|
||||
valid_extensions = ('.png', '.jpg', '.jpeg', '.bmp', '.tif')
|
||||
file_list = [f for f in os.listdir(src_folder) if f.lower().endswith(valid_extensions)]
|
||||
|
||||
total_files = len(file_list)
|
||||
print(f"共发现 {total_files} 张图片,准备开始并行处理 (进程数: {max_workers if max_workers else '自动'})...\n")
|
||||
|
||||
# 3. 并行处理
|
||||
results = []
|
||||
|
||||
# ProcessPoolExecutor 自动管理进程池
|
||||
# max_workers=None 意味着使用 CPU 核心数
|
||||
with ProcessPoolExecutor(max_workers=max_workers) as executor:
|
||||
# 提交所有任务
|
||||
future_to_file = {
|
||||
executor.submit(process_single_image, filename, src_folder, ref_folder, output_folder): filename
|
||||
for filename in file_list
|
||||
}
|
||||
|
||||
# 使用 tqdm 显示进度条,as_completed 会在任何一个任务完成时yield
|
||||
pbar = tqdm(total=total_files, unit="img")
|
||||
|
||||
for future in as_completed(future_to_file):
|
||||
result_msg = future.result()
|
||||
pbar.update(1)
|
||||
|
||||
# 如果是错误或跳过信息,打印出来;如果是OK,只更新进度条不刷屏(可选)
|
||||
if not result_msg.startswith("OK"):
|
||||
tqdm.write(result_msg) # 使用 tqdm.write 防止打断进度条
|
||||
# else:
|
||||
# tqdm.write(result_msg) # 如果想看每张图的详细结果,取消注释这行
|
||||
|
||||
pbar.close()
|
||||
|
||||
print("\n" + "="*30)
|
||||
print("所有处理已完成。")
|
||||
|
||||
# 执行
|
||||
if __name__ == "__main__":
|
||||
# Windows 下使用多进程必须放在 if __name__ == "__main__": 之下
|
||||
if os.path.exists(src_dir) and os.path.exists(ref_dir):
|
||||
# max_workers 可以手动指定,例如 max_workers=4。如果不填则默认跑满 CPU。
|
||||
calculate_and_process_batch_parallel(src_dir, ref_dir, out_dir, max_workers = max_workers)
|
||||
else:
|
||||
print("错误: 找不到输入文件夹或参考文件夹,请检查路径。")
|
||||
161
※程序-后处理汇总/2_Adjust_V3_SV直方图调整+SV自动调整_效果一般.py
Normal file
161
※程序-后处理汇总/2_Adjust_V3_SV直方图调整+SV自动调整_效果一般.py
Normal file
@@ -0,0 +1,161 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from skimage import color, exposure
|
||||
from scipy.optimize import minimize
|
||||
import os
|
||||
import time
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
|
||||
# ================= 配置区域 =================
|
||||
|
||||
# 1. 输入与输出文件夹
|
||||
SRC_DIR = "去雾图像-北航合作-Result_Baidu" # 待处理图片文件夹
|
||||
REF_DIR = "去雾图像-北航合作" # 参考图(GT)文件夹
|
||||
OUT_DIR = "去雾图像-北航合作-Result_Baidu_Own_V3" # 结果输出文件夹
|
||||
|
||||
# 2. 功能开关
|
||||
ENABLE_HIST_MATCH = True # 【开关】 True: 开启直方图匹配; False: 关闭
|
||||
MAX_WORKERS = 4 # 【并行】 并行处理的进程数 (建议设为 CPU 核心数,如 4, 8, 16)
|
||||
|
||||
# ===========================================
|
||||
|
||||
def process_single_image(file_info):
|
||||
"""
|
||||
单个图片处理函数 (用于并行调用)
|
||||
file_info: (filename, src_dir, ref_dir, out_dir, enable_hist)
|
||||
"""
|
||||
filename, src_folder, ref_folder, output_folder, use_hist = file_info
|
||||
|
||||
source_path = os.path.join(src_folder, filename)
|
||||
|
||||
# 1. 寻找对应的参考图
|
||||
# 逻辑:去除文件名后缀 "_result" (例如 "image01_result.png" -> "image01.png")
|
||||
name_no_ext, ext = os.path.splitext(filename)
|
||||
if name_no_ext.endswith("_result"):
|
||||
ref_name_no_ext = name_no_ext[:-7] # 去掉 "_result"
|
||||
else:
|
||||
ref_name_no_ext = name_no_ext
|
||||
|
||||
ref_filename = ref_name_no_ext + ext
|
||||
ref_path = os.path.join(ref_folder, ref_filename)
|
||||
|
||||
if not os.path.exists(ref_path):
|
||||
return f"[跳过] 找不到参考图: {filename}"
|
||||
|
||||
try:
|
||||
# 2. 读取图片并归一化 (0-1 float)
|
||||
img_src_pil = Image.open(source_path).convert('RGB')
|
||||
img_src = np.array(img_src_pil) / 255.0
|
||||
|
||||
img_ref_pil = Image.open(ref_path).convert('RGB')
|
||||
if img_src_pil.size != img_ref_pil.size:
|
||||
img_ref_pil = img_ref_pil.resize(img_src_pil.size, Image.BILINEAR)
|
||||
img_ref = np.array(img_ref_pil) / 255.0
|
||||
|
||||
# 3. RGB -> HSV
|
||||
hsv_src = color.rgb2hsv(img_src)
|
||||
hsv_ref = color.rgb2hsv(img_ref)
|
||||
|
||||
# === 新增功能: 直方图匹配 (Histogram Matching) ===
|
||||
if use_hist:
|
||||
# 分离通道
|
||||
s_h, s_s, s_v = hsv_src[:,:,0], hsv_src[:,:,1], hsv_src[:,:,2]
|
||||
r_h, r_s, r_v = hsv_ref[:,:,0], hsv_ref[:,:,1], hsv_ref[:,:,2]
|
||||
|
||||
# 对 S 和 V 通道进行直方图匹配
|
||||
# 这会将 src 的分布形状强行调整为 ref 的分布形状
|
||||
matched_s = exposure.match_histograms(s_s, r_s)
|
||||
matched_v = exposure.match_histograms(s_v, r_v)
|
||||
|
||||
# 更新 hsv_src,后续的 minimize 将在此基础上进一步微调系数
|
||||
hsv_src = np.stack([s_h, matched_s, matched_v], axis=-1)
|
||||
|
||||
# 4. 优化 S/V 乘数因子
|
||||
# 即使做了直方图匹配,我们依然计算一个最佳的整体缩放系数,以确保整体误差最小
|
||||
def loss_function(params):
|
||||
ks, kv = params
|
||||
adj_s = np.clip(hsv_src[:,:,1] * ks, 0, 1)
|
||||
adj_v = np.clip(hsv_src[:,:,2] * kv, 0, 1)
|
||||
loss_s = np.mean((adj_s - hsv_ref[:,:,1])**2)
|
||||
loss_v = np.mean((adj_v - hsv_ref[:,:,2])**2)
|
||||
return loss_s + loss_v
|
||||
|
||||
# 初始猜测 [1.0, 1.0]
|
||||
res = minimize(loss_function, [1.0, 1.0], method='Nelder-Mead', tol=1e-4)
|
||||
best_s, best_v = res.x
|
||||
|
||||
s_percent = int(best_s * 100)
|
||||
v_percent = int(best_v * 100)
|
||||
|
||||
# 5. 应用最终参数
|
||||
hsv_final = hsv_src.copy()
|
||||
hsv_final[:, :, 1] = np.clip(hsv_final[:, :, 1] * best_s, 0, 1)
|
||||
hsv_final[:, :, 2] = np.clip(hsv_final[:, :, 2] * best_v, 0, 1)
|
||||
|
||||
# 6. 保存结果
|
||||
img_result_rgb = color.hsv2rgb(hsv_final)
|
||||
img_save = Image.fromarray((img_result_rgb * 255).astype(np.uint8))
|
||||
|
||||
# 命名增加标识,如果开启了直方图匹配,可以在文件名加个标记(可选),
|
||||
# 这里保持您要求的格式: 原文件名_S_XX_V_XX.png
|
||||
new_filename = f"{name_no_ext}_S_{s_percent}_V_{v_percent}{ext}"
|
||||
save_path = os.path.join(output_folder, new_filename)
|
||||
|
||||
img_save.save(save_path)
|
||||
|
||||
match_tag = "[HistMatch]" if use_hist else "[Raw]"
|
||||
return f"{match_tag} 完成: {new_filename} (S={s_percent}%, V={v_percent}%)"
|
||||
|
||||
except Exception as e:
|
||||
return f"[错误] {filename}: {str(e)}"
|
||||
|
||||
def main():
|
||||
# 1. 检查文件夹
|
||||
if not os.path.exists(SRC_DIR) or not os.path.exists(REF_DIR):
|
||||
print("错误: 输入或参考文件夹不存在。")
|
||||
return
|
||||
|
||||
if not os.path.exists(OUT_DIR):
|
||||
os.makedirs(OUT_DIR)
|
||||
|
||||
# 2. 获取文件列表
|
||||
valid_extensions = ('.png', '.jpg', '.jpeg', '.bmp', '.tif')
|
||||
file_list = [f for f in os.listdir(SRC_DIR) if f.lower().endswith(valid_extensions)]
|
||||
total_files = len(file_list)
|
||||
|
||||
if total_files == 0:
|
||||
print("源文件夹为空。")
|
||||
return
|
||||
|
||||
print(f"=== 开始处理 ===")
|
||||
print(f"模式: {'直方图匹配 + 参数优化' if ENABLE_HIST_MATCH else '仅参数优化'}")
|
||||
print(f"并行: {MAX_WORKERS} 线程")
|
||||
print(f"数量: {total_files} 张图片")
|
||||
print("-" * 30)
|
||||
|
||||
# 3. 准备任务参数
|
||||
tasks = []
|
||||
for f in file_list:
|
||||
# 打包参数传给 worker
|
||||
tasks.append((f, SRC_DIR, REF_DIR, OUT_DIR, ENABLE_HIST_MATCH))
|
||||
|
||||
# 4. 并行执行
|
||||
start_time = time.time()
|
||||
|
||||
with ProcessPoolExecutor(max_workers=MAX_WORKERS) as executor:
|
||||
# 提交所有任务
|
||||
futures = [executor.submit(process_single_image, task) for task in tasks]
|
||||
|
||||
# 获取结果 (as_completed 会在任务完成时立即返回)
|
||||
for i, future in enumerate(as_completed(futures)):
|
||||
result = future.result()
|
||||
print(f"[{i+1}/{total_files}] {result}")
|
||||
|
||||
end_time = time.time()
|
||||
print("-" * 30)
|
||||
print(f"全部完成! 耗时: {end_time - start_time:.2f} 秒")
|
||||
print(f"结果保存在: {OUT_DIR}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Windows 下使用多进程必须放在 if __name__ == "__main__": 之下
|
||||
main()
|
||||
Reference in New Issue
Block a user