numpy转PIL.Image: 处理Mask图像为单通道的彩色/灰度图colormap.png

本文深入探讨图像位深的概念,解析8位彩色图、8位灰度图及1位二值图的特点,并通过实例说明如何使用PIL库正确读取和处理不同位深的Mask图像,特别关注在深度学习分割任务中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


从分割数据集说起

8位彩色图

下图是 NYU数据集 里的一张Mask图像, 显示彩图, 但实际是单通道位深为8的png图像, 也就是单通道图像, 并不是常见的RGB三通道.
在这里插入图片描述
而这种图像怎么读取呢? 用opencv读取显然是行不通的, 这点在另一篇博客有过介绍, 因为改变了图像的像素值和格式, 参考在深度学习中遇到的opencv坑.
在这里插入图片描述
推荐使用PIL.Image.open()读取. 可以看到像素值在 [0, 255] 之间, 矩阵仍然是二维单通道.
在这里插入图片描述
再看一下具体的像素值, 可以通过统计元素的方法得到: (参考在深度学习中遇到的opencv坑)
px = [255, 7, 0, 37, 39, 6, 25, 4, 28]
这些值也是实例类别的标签!!! 因此不能使用opencv读取, 不能被改变.

8位灰度图

下图是 GTEA 数据集中的一张Mask图像, 显示为灰度图, 实际也是单通道 位深为8的png图像
在这里插入图片描述
使用PIL.Image.open()读取. 可以看到像素值在 [0, 255] 之间, 矩阵仍然是二维单通道.
在这里插入图片描述
统计具体的像素值, 只有两个:
px = [0, 200]
对于二值Mask图像来说, 这种情况是需要处理的, 可以读取该数据集的其他图像, 能够看出像素值并不统一, 而分割只有两种: 手和背景. 因此需要将图像标签处理为 0/1, 本文讨论的重点就是如何处理这类不规则格式的Mask图像

1位二值图

下图是 HandOverFace数据集的一张图像, 显示为二值图, 实际是单通道 位深为1的png图像, 这个格式就比较统一, 但仍然存在麻烦.
在这里插入图片描述
使用PIL.Image.open()读取. 可以看到像素值是bool型, 矩阵是二维单通道.
在这里插入图片描述
这种数据集仍然需要处理.

什么是图像位深

图像位深, 又叫色深(Color Depth),指像素点可以有多少种色彩来描述,单位是“bit”(位), 如1-bit、8-bit、24-bit和32-bit. 深度数值越高,可以获得更多的色彩.

  • 1位图像, 位深度是1, 含有 2 1 2^1 21 种颜色或灰度级;
  • 8位图像,位深度是8,含有 2 8 = 256 2^8=256 28=256 种颜色或灰度级.;
  • 24位图像可称为真彩色图像,位深度是24,能组合成 2 24 = 1677 , 7216 2^{24}=1677,7216 224=1677,7216 种颜色,超过了人眼能够分辨的颜色数量. 实际上, 计算机是以RGB三通道的形式来存储的, 每一通道都是8位, 可指示256种颜色, 三基色相互组合就形成了 ( 2 8 ) 3 (2^8)^3 (28)3 种颜色;
  • 32位图像是在RGB图像基础上增加了 α \alpha α通道, 代表还存在256级透明度;
  • 还存在不规则的2位/4位/x位等图像, 即含有 2 x 2^x 2x 种颜色或灰度级.

所以, 图像的像素值即指示了所在像素是哪一种颜色/哪一级灰度, 这里存在一种"数值-颜色"的索引关系, 称为 调色板. 对于最常见的RGB图像, 我们知道像素值[0,0,0]是黑色, [255,255,255]是白色; 对于1位图像, 像素值[0]则代表黑色, [1]代表白色; 但是对于8位图像, 则需要看调色板索引的实际颜色, 其中像素值[0]代表了调色板定义的第0种颜色, [255]代表第255种颜色.

因此, 和上述数据集Mask图像的情形一致, 同样的单通道矩阵, 如果调色板配置不同, 所呈现的图像也是不一样的.

PNG图像格式简介

参考: PNG - 维基百科

  • PNG(Portable Network Graphics,便携式网络图形)是一种无损压缩的位图图形格式,支持索引、灰度、RGB三种颜色方案以及Alpha通道等特性.

  • PNG图像的文件结构包括一个8字节的PNG文件标识域和3个以上的后续数据块组成, 共有两种类型的数据块:一种是PNG文件必须包含、读写软件必须支持的关键块(critical chunk);另一种是辅助块(ancillary chunks). PNG允许软件忽略它不认识的附加块, 这种基于数据块的设计, 使得PNG格式在扩展时仍能保持与旧版本兼容.

  • 关键数据块中有4个部分:
    文件头数据块 IHDR(header chunk):包含有图像基本信息,作为第一个数据块出现并只出现一次;
    调色板数据块 PLTE(palette chunk):必须放在图像数据块之前;
    图像数据块 IDAT(image data chunk):存储实际图像数据。PNG数据允许包含多个连续的图像数据块;
    图像结束数据 IEND(image trailer chunk):放在文件尾部,表示PNG数据流结束.

如何处理Mask图像

在语义分割/实例分割中, Mask图像的像素值代表了像素点所属的类别标签. 对于单个类别的数据集来说, Mask像素值应该是[0/1], 显示为黑色/白色以区分背景/目标; 对于多个类别的数据集, Mask像素值应该是[0,1,2,…], 显示为彩色以区分不同目标. 这就需要用到调色板索引.

考虑到通用性, 不使用1位图像[False/True], 不使用不规则的位深, 推荐Mask图像都转为8-bits二值图或彩色图. 当然, 不额外处理数据集也是可行的, 只需要在读取时对像素值做转换即可.

处理代码如下:

import numpy as np
import PIL.Image as Image

src= Image.open("1.png")
mat = np.array(src)
mat = mat.astype(np.uint8)
dst = Image.fromarray(mat, 'P')
bin_colormap = [0,0,0] + [255,255,255]*254    # 二值调色板
dst.putpalette(bin_colormap)
dst.save('new.png')
src.close()

在这里插入图片描述

cmap = np.load('../utils/cmap.npy')
dst.putpalette(cmap)

在这里插入图片描述

注意!!!PIL一定要手动close 防止内存泄漏

PIL库相关函数

增加一个选择项,用户可按照是否存在异常亮度区域选择照片进行亮度查看 import os import cv2 import numpy as np import screeninfo from PIL import Image, ImageDraw, ImageFont class EnhancedImageViewer: """增强版图像查看器,支持亮度查看(Lux值)""" def __init__(self, image_array, title="Image View", base_output_name=None): # 直接使用图像数组而不是文件路径 self.original_image = image_array if self.original_image is None: raise ValueError("无法加载图像数组") # 保存基础输出名称 self.base_output_name = base_output_name if base_output_name else "output" # 获取灰度图像用于亮度分析 self.gray_image = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2GRAY) # 获取屏幕尺寸 try: screen = screeninfo.get_monitors()[0] self.max_width = screen.width - 100 self.max_height = screen.height - 100 except: self.max_width = 1080 self.max_height = 1080 # 初始缩放状态 self.scale_factor = 1.0 self.offset_x = 0 self.offset_y = 0 # 创建显示图像 self.display_image = self.original_image.copy() self.resized_display = self.resize_to_fit(self.original_image) # 创建窗口 self.window_name = title cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL) cv2.setMouseCallback(self.window_name, self.mouse_callback) # 更新显示 self.update_display() def resize_to_fit(self, image): """调整图像尺寸以适应屏幕""" height, width = image.shape[:2] scale_width = self.max_width / width scale_height = self.max_height / height self.scale_factor = min(scale_width, scale_height, 1.0) new_width = int(width * self.scale_factor) new_height = int(height * self.scale_factor) if self.scale_factor < 1.0: return cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_AREA) return image.copy() def put_chinese_text(self, image, text, position, font_size=20, color=(255, 255, 255)): """在图像上添加中文文本""" if image is None or image.size == 0: return image # 确保图像是3通道的 if len(image.shape) == 2: image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) # 换为PIL图像 (RGB格式) pil_img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(pil_img) # 使用支持中文的字体 try: font = ImageFont.truetype("simhei.ttf", font_size) except: try: font = ImageFont.truetype("msyh.ttc", font_size) except: try: font = ImageFont.truetype("/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", font_size) except: font = ImageFont.load_default() # 添加文本 draw.text(position, text, font=font, fill=color) # 换回OpenCV格式 return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) def update_display(self): """更新显示图像""" # 缩放小于等于1.0时直接显示 self.display_image = self.resized_display.copy() # 添加帮助文本 help_text = "左键:查看亮度(Lux) | ESC:退出 | R:重置 | S:保存" self.display_image = self.put_chinese_text( self.display_image, help_text, (10, 20), font_size=20, color=(0, 255, 0) ) # 显示图像 cv2.imshow(self.window_name, self.display_image) def mouse_callback(self, event, x, y, flags, param): """鼠标事件回调函数 - 仅保留左键点击功能""" # 鼠标左键点击 - 显示亮度值(Lux) if event == cv2.EVENT_LBUTTONDOWN: # 计算原始图像坐标 orig_x, orig_y = self.convert_coords(x, y) # 获取原始图像中的灰度值 if 0 <= orig_x < self.gray_image.shape[1] and 0 <= orig_y < self.gray_image.shape[0]: gray_value = self.gray_image[orig_y, orig_x] # 将灰度值换为Lux值 (0-255范围映射到Lux) lux_value = int((gray_value / 255.0) * 660) # 在当前显示图像上添加标记和信息 display_copy = self.display_image.copy() cv2.circle(display_copy, (x, y), 5, (0, 0, 255), -1) # 计算文本位置(避免遮挡) text_x = x + 10 text_y = y - 10 # 如果靠近右侧边缘,向左移动文本 if text_x > self.display_image.shape[1] - 280: text_x = x - 280 # 如果靠近底部边缘,向上移动文本 if text_y < 30: text_y = y + 20 # 添加中文文本(显示Lux值) text = f"位置: ({orig_x}, {orig_y}) 亮度: {lux_value} Lux" display_copy = self.put_chinese_text( display_copy, text, (text_x, text_y), font_size=18, color=(0, 255, 255) ) cv2.imshow(self.window_name, display_copy) def convert_coords(self, x, y): """将显示坐标换为原始图像坐标""" # 缩小或正常状态下的坐标换 orig_x = int(x / self.scale_factor) orig_y = int(y / self.scale_factor) # 确保坐标在有效范围内 orig_x = max(0, min(orig_x, self.original_image.shape[1] - 1)) orig_y = max(0, min(orig_y, self.original_image.shape[0] - 1)) return orig_x, orig_y def run(self): """运行查看器主循环""" while True: key = cv2.waitKey(1) & 0xFF if key == 27: # ESC退出 break elif key == ord('r'): # 重置视图 self.scale_factor = 1.0 self.offset_x = 0 self.offset_y = 0 self.resized_display = self.resize_to_fit(self.original_image) self.update_display() elif key == ord('s'): # 保存当前视图 # 使用基础输出名称生成截图文件名 screenshot_path = f"{self.base_output_name}_screenshot.png" cv2.imwrite(screenshot_path, self.display_image) print(f"截图已保存为 {screenshot_path}") cv2.destroyAllWindows() def apply_gaussian_blur(image, kernel_size=(5, 5), sigma=1): """应用高斯模糊""" return cv2.GaussianBlur(image, kernel_size, sigma) def preprocess_image(image_path): """图像处理""" image = cv2.imread(image_path) if image is None: raise FileNotFoundError(f"无法加载图像: {image_path}") # 应用高斯模糊 blurred_image = apply_gaussian_blur(image) # 换为灰度图并降噪 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred_gray = cv2.GaussianBlur(gray, (3, 3), 1) # 对比度增强 clahe = cv2.createCLAHE(clipLimit=9.0, tileGridSize=(21, 21)) enhanced = clahe.apply(blurred_gray) return image, gray, enhanced, blurred_image def analyze_intensity(enhanced): """亮度分析""" min_intensity = np.min(enhanced) max_intensity = np.max(enhanced) normalized = cv2.normalize(enhanced.astype('float'), None, 0, 1, cv2.NORM_MINMAX) return min_intensity, max_intensity, normalized def generate_heatmap(image, normalized): """生成热力图""" heatmap = cv2.applyColorMap((normalized * 255).astype(np.uint8), cv2.COLORMAP_JET) blended = cv2.addWeighted(image, 0.7, heatmap, 0.3, 0) return heatmap, blended def process_contours(image, enhanced, min_intensity, max_intensity, num_levels=7): """处理等高线""" height, width = image.shape[:2] contour_image = np.zeros_like(image) color_intensity_map = [] # 计算亮度层级 levels = np.linspace(min_intensity, max_intensity, num_levels).astype(np.uint8) kernel = np.ones((3, 3), np.uint8) for i, level in enumerate(levels): # 创建当前亮度层级的掩膜 lower_val = max(int(level) - 10, 0) upper_val = min(int(level) + 10, 255) mask = cv2.inRange(enhanced, lower_val, upper_val) # 形态学处理 mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 查找等高线 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 计算颜色(冷色调到暖色调渐变) normalized_level = (level - min_intensity) / (max_intensity - min_intensity) if normalized_level < 0.5: hue = 120 + 60 * normalized_level * 2 else: hue = 60 * (1 - (normalized_level - 0.5) * 2) hue = np.clip(hue, 0, 180) # 换为BGR颜色 color = cv2.cvtColor(np.uint8([[[hue, 255, 255]]]), cv2.COLOR_HSV2BGR)[0][0] color = tuple(map(int, color)) # 存储颜色-亮度映射 # 将灰度值换为Lux值 (0-255范围映射到 Lux) lux_value = int((level / 255.0) * 660) color_intensity_map.append((color, lux_value)) # 绘制等高线 cv2.drawContours(contour_image, contours, -1, color, 2) return contour_image, color_intensity_map def create_color_bar(image, color_intensity_map, min_intensity, max_intensity): """创建颜色条 - 显示Lux值""" height, width = image.shape[:2] bar_width = int(width * 0.01) bar_height = int(height * 0.3) bar_x = width - int(width * 0.05) - bar_width bar_y = int(height * 0.05) # 生成颜色条 color_bar = np.zeros((bar_height, bar_width, 3), dtype=np.uint8) for i in range(bar_height): idx = int((1 - i / bar_height) * (len(color_intensity_map) - 1)) color_bar[i, :] = color_intensity_map[idx][0] # 添加到图像 result = image.copy() result[bar_y:bar_y + bar_height, bar_x:bar_x + bar_width] = color_bar # 添加边框 cv2.rectangle(result, (bar_x, bar_y), (bar_x + bar_width, bar_y + bar_height), (255, 255, 255), 1) # 添加刻度和标签(显示Lux值) num_ticks = 5 min_lux = 0 max_lux = 2200 for i, pos in enumerate(np.linspace(0, bar_height, num_ticks)): y_pos = int(bar_y + pos) cv2.line(result, (bar_x - 5, y_pos), (bar_x, y_pos), (255, 255, 255), 1) # 计算标签位置 value = int(min_lux + (max_lux - min_lux) * (1 - pos / bar_height)) text_x = bar_x - 50 text_y = y_pos + (15 if i == 0 else -10 if i == num_ticks - 1 else 0) # 添加带描边的文本 cv2.putText(result, f"{value} Lux", (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 2) cv2.putText(result, f"{value} Lux", (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) # 添加标题 cv2.putText(result, 'Light Intensity (Lux)', (bar_x - 150, bar_y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 2) cv2.putText(result, 'Light Intensity (Lux)', (bar_x - 150, bar_y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) return result def main(image_path): """主处理流程""" try: # 从路径中提取基础文件名(不含扩展名) base_name = os.path.splitext(os.path.basename(image_path))[0] # 1. 图像处理 image, gray, enhanced, blurred_image = preprocess_image(image_path) # 2. 亮度分析 min_intensity, max_intensity, normalized = analyze_intensity(enhanced) # 3. 生成热力图 heatmap, blended = generate_heatmap(image, normalized) # 4. 处理等高线 contour_image, color_intensity_map = process_contours( image, enhanced, min_intensity, max_intensity ) # 5. 创建最终结果(显示Lux值) base_image = cv2.addWeighted(image, 0.7, contour_image, 0.3, 0) # 使用固定的 Lux范围 min_lux = 0 max_lux = 660 final_result = create_color_bar(base_image, color_intensity_map, min_lux, max_lux) # 保存结果(使用基础文件名作为前缀) cv2.imwrite(f"{base_name}_result.png", final_result) cv2.imwrite(f"{base_name}_Contours.png", contour_image) cv2.imwrite(f"{base_name}_Heatmap.png", heatmap) cv2.imwrite(f"{base_name}_Blended.png", blended) cv2.imwrite(f"{base_name}_blurred_image.png", blurred_image) print(f"处理完成! 结果已保存为 {base_name}_*.png") # 启动交互式查看器(传递高斯模糊图像数组) viewer = EnhancedImageViewer(blurred_image, "Image View", base_output_name=base_name) viewer.run() except Exception as e: print(f"处理过程中发生错误: {str(e)}") if __name__ == "__main__": # 修改这里的图片路径 image_path = 'photo_031.jpg' main(image_path) 将程序中的亮度变成用户在主程序修改或者根据提示修改
最新发布
07-23
import os import cv2 import numpy as np import screeninfo from PIL import Image, ImageDraw, ImageFont import time from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler import os import logging # 配置日志 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') class EnhancedImageViewer: """增强版图像查看器,支持亮度查看(Lux值)""" def __init__(self, image_path, title="照片亮度查看器", base_output_name=None): self.image_path = image_path self.original_image = cv2.imread(image_path) if self.original_image is None: raise FileNotFoundError(f"无法加载图像: {image_path}") # 保存基础输出名称 self.base_output_name = base_output_name if base_output_name else \ os.path.splitext(os.path.basename(image_path))[0] # 获取灰度图像用于亮度分析 self.gray_image = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2GRAY) # 获取屏幕尺寸 try: screen = screeninfo.get_monitors()[0] self.max_width = screen.width - 200 self.max_height = screen.height - 200 except: self.max_width = 1080 self.max_height = 1080 # 初始缩放状态 self.scale_factor = 1.0 self.offset_x = 0 self.offset_y = 0 # 创建显示图像 self.display_image = self.original_image.copy() self.resized_display = self.resize_to_fit(self.original_image) # 创建窗口 self.window_name = title cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL) cv2.setMouseCallback(self.window_name, self.mouse_callback) # 更新显示 self.update_display() def resize_to_fit(self, image): """调整图像尺寸以适应屏幕""" height, width = image.shape[:2] scale_width = self.max_width / width scale_height = self.max_height / height self.scale_factor = min(scale_width, scale_height, 1.0) new_width = int(width * self.scale_factor) new_height = int(height * self.scale_factor) if self.scale_factor < 1.0: return cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_AREA) return image.copy() def put_chinese_text(self, image, text, position, font_size=20, color=(255, 255, 255)): """在图像上添加中文文本""" if image is None or image.size == 0: return image # 确保图像是3通道的 if len(image.shape) == 2: image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) # 换为PIL图像 (RGB格式) pil_img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(pil_img) # 使用支持中文的字体 try: font = ImageFont.truetype("simhei.ttf", font_size) except: try: font = ImageFont.truetype("msyh_boot.ttf", font_size) except: try: font = ImageFont.truetype("/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", font_size) except: font = ImageFont.load_default() # 添加文本 draw.text(position, text, font=font, fill=color) # 换回OpenCV格式 return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) def update_display(self): """更新显示图像""" # 缩放小于等于1.0时直接显示 self.display_image = self.resized_display.copy() # 添加帮助文本 help_text = "左键:查看亮度(Lux) | ESC:退出 | R:重置 | S:保存" self.display_image = self.put_chinese_text( self.display_image, help_text, (10, 20), font_size=20, color=(0, 255, 0) ) # 显示图像 cv2.imshow(self.window_name, self.display_image) def mouse_callback(self, event, x, y, flags, param): """鼠标事件回调函数 - 仅保留左键点击功能""" # 鼠标左键点击 - 显示亮度值(Lux) if event == cv2.EVENT_LBUTTONDOWN: # 计算原始图像坐标 orig_x, orig_y = self.convert_coords(x, y) # 获取原始图像中的灰度值 if 0 <= orig_x < self.gray_image.shape[1] and 0 <= orig_y < self.gray_image.shape[0]: gray_value = self.gray_image[orig_y, orig_x] # 将灰度值换为Lux值 (0-255范围映射到Lux) lux_value = int((gray_value / 255.0) * 660) # 在当前显示图像上添加标记和信息 display_copy = self.display_image.copy() cv2.circle(display_copy, (x, y), 5, (0, 0, 255), -1) # 计算文本位置(避免遮挡) text_x = x + 10 text_y = y - 10 # 如果靠近右侧边缘,向左移动文本 if text_x > self.display_image.shape[1] - 250: text_x = x - 250 # 如果靠近底部边缘,向上移动文本 if text_y < 30: text_y = y + 20 # 添加中文文本(显示Lux值) text = f"位置: ({orig_x}, {orig_y}) 亮度: {lux_value} Lux" display_copy = self.put_chinese_text( display_copy, text, (text_x, text_y), font_size=18, color=(0, 255, 255) ) cv2.imshow(self.window_name, display_copy) def convert_coords(self, x, y): """将显示坐标换为原始图像坐标""" # 缩小或正常状态下的坐标换 orig_x = int(x / self.scale_factor) orig_y = int(y / self.scale_factor) # 确保坐标在有效范围内 orig_x = max(0, min(orig_x, self.original_image.shape[1] - 1)) orig_y = max(0, min(orig_y, self.original_image.shape[0] - 1)) return orig_x, orig_y def run(self): """运行查看器主循环""" while True: key = cv2.waitKey(1) & 0xFF if key == 27: # ESC退出 break elif key == ord('r'): # 重置视图 self.scale_factor = 1.0 self.offset_x = 0 self.offset_y = 0 self.resized_display = self.resize_to_fit(self.original_image) self.update_display() elif key == ord('s'): # 保存当前视图 # 使用基础输出名称生成截图文件名 screenshot_path = f"{self.base_output_name}_screenshot.png" cv2.imwrite(screenshot_path, self.display_image) print(f"截图已保存为 {screenshot_path}") cv2.destroyAllWindows() def preprocess_image(image_path): """图像处理""" image = cv2.imread(image_path) if image is None: raise FileNotFoundError(f"无法加载图像: {image_path}") # 换为灰度图并降噪 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (3, 3), 0) # 对比度增强 clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(8, 8)) enhanced = clahe.apply(blurred) return image, gray, enhanced def analyze_intensity(enhanced): """亮度分析""" min_intensity = np.min(enhanced) max_intensity = np.max(enhanced) normalized = cv2.normalize(enhanced.astype('float'), None, 0, 1, cv2.NORM_MINMAX) return min_intensity, max_intensity, normalized def generate_heatmap(image, normalized): """生成热力图""" heatmap = cv2.applyColorMap((normalized * 255).astype(np.uint8), cv2.COLORMAP_JET) blended = cv2.addWeighted(image, 0.7, heatmap, 0.3, 0) return heatmap, blended def process_contours(image, enhanced, min_intensity, max_intensity, num_levels=7): """处理等高线""" height, width = image.shape[:2] contour_image = np.zeros_like(image) color_intensity_map = [] # 计算亮度层级 levels = np.linspace(min_intensity, max_intensity, num_levels).astype(np.uint8) kernel = np.ones((3, 3), np.uint8) for i, level in enumerate(levels): # 创建当前亮度层级的掩膜 lower_val = max(int(level) - 10, 0) upper_val = min(int(level) + 10, 255) mask = cv2.inRange(enhanced, lower_val, upper_val) # 形态学处理 mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 查找等高线 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 计算颜色(冷色调到暖色调渐变) normalized_level = (level - min_intensity) / (max_intensity - min_intensity) if normalized_level < 0.5: hue = 120 + 60 * normalized_level * 2 else: hue = 60 * (1 - (normalized_level - 0.5) * 2) hue = np.clip(hue, 0, 180) # 换为BGR颜色 color = cv2.cvtColor(np.uint8([[[hue, 255, 255]]]), cv2.COLOR_HSV2BGR)[0][0] color = tuple(map(int, color)) # 存储颜色-亮度映射 # 将灰度值换为Lux值 (0-255范围映射到0-740 Lux) lux_value = int((level / 255.0) * 660) color_intensity_map.append((color, lux_value)) # 绘制等高线 cv2.drawContours(contour_image, contours, -1, color, 2) return contour_image, color_intensity_map def create_color_bar(image, color_intensity_map, min_intensity, max_intensity): """创建颜色条 - 显示Lux值""" height, width = image.shape[:2] bar_width = int(width * 0.03) bar_height = int(height * 0.3) bar_x = width - int(width * 0.05) - bar_width bar_y = int(height * 0.05) # 生成颜色条 color_bar = np.zeros((bar_height, bar_width, 3), dtype=np.uint8) for i in range(bar_height): idx = int((1 - i / bar_height) * (len(color_intensity_map) - 1)) color_bar[i, :] = color_intensity_map[idx][0] # 添加到图像 result = image.copy() result[bar_y:bar_y + bar_height, bar_x:bar_x + bar_width] = color_bar # 添加边框 cv2.rectangle(result, (bar_x, bar_y),(bar_x + bar_width, bar_y + bar_height), (255, 255, 255), 1) # 添加刻度和标签(显示Lux值) num_ticks = 5 min_lux = 0 max_lux = 660 for i, pos in enumerate(np.linspace(0, bar_height, num_ticks)): y_pos = int(bar_y + pos) cv2.line(result, (bar_x - 5, y_pos), (bar_x, y_pos), (255, 255, 255), 1) # 计算标签位置 value = int(min_lux + (max_lux - min_lux) * (1 - pos / bar_height)) text_x = bar_x - 50 text_y = y_pos + (15 if i == 0 else -10 if i == num_ticks - 1 else 0) # 添加带描边的文本 cv2.putText(result, f"{value} Lux", (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 2) cv2.putText(result, f"{value} Lux", (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) # 添加标题 cv2.putText(result, 'Light Intensity (Lux)', (bar_x - 150, bar_y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 2) cv2.putText(result, 'Light Intensity (Lux)', (bar_x - 150, bar_y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) return result class ImageHandler(FileSystemEventHandler): def __init__(self, processing_function): self.processing_function = processing_function self.supported_exts = ['.jpg'] #, '.jpeg', '.png', '.bmp', '.tiff' def on_created(self, event): """当新文件创建时触发""" if not event.is_directory: file_path = event.src_path if self._is_image(file_path): logging.info(f"检测到新图片: {file_path}") # 等待文件完全写入(根据实际需求调整) time.sleep(0.5) self.processing_function(file_path) def _is_image(self, file_path): """检查是否为支持的图片格式""" ext = os.path.splitext(file_path)[1].lower() return ext in self.supported_exts def start_monitoring(path_to_watch, processing_function): """启动文件夹监控服务""" event_handler = ImageHandler(processing_function) observer = Observer() observer.schedule(event_handler, path_to_watch, recursive=False) observer.start() logging.info(f"开始监控文件夹: {path_to_watch}") try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() def main(image_path): """主处理流程""" try: # 从路径中提取基础文件名(不含扩展名) base_name = os.path.splitext(os.path.basename(image_path))[0] # 1. 图像处理 image, gray, enhanced = preprocess_image(image_path) # 2. 亮度分析 min_intensity, max_intensity, normalized = analyze_intensity(enhanced) # 3. 生成热力图 heatmap, blended = generate_heatmap(image, normalized) # 4. 处理等高线 contour_image, color_intensity_map = process_contours( image, enhanced, min_intensity, max_intensity ) # 5. 创建最终结果(显示Lux值) base_image = cv2.addWeighted(image, 0.7, contour_image, 0.3, 0) # 使用固定的 Lux范围 min_lux = 0 max_lux = 660 final_result = create_color_bar(base_image, color_intensity_map, min_lux, max_lux) # 保存结果(使用基础文件名作为前缀) cv2.imwrite(f"{base_name}_result.png", final_result) cv2.imwrite(f"{base_name}_Contours.png", contour_image) cv2.imwrite(f"{base_name}_Heatmap.png", heatmap) cv2.imwrite(f"{base_name}_Blended.png", blended) print(f"处理完成! 结果已保存为 {base_name}_*.png") # 启动交互式查看器(传递基础文件名) viewer = EnhancedImageViewer(image_path, 'photo_view', base_output_name=base_name) viewer.run() except Exception as e: print(f"处理过程中发生错误: {str(e)}") if __name__ == "__main__": # 修改这里的图片路径 image_path = 'photo_031.jpg' main(image_path) 照片中有一块异常高亮度的区域,在840-2200lux,其他地方最大的亮度为660,如何处理这种情况
07-19
<think>好的,用户的问题是如何将Pascal VOC数据集中的彩色标签图像换为SegmentationClassAug格式的灰度图。我需要先理解这两种格式的区别以及换的关键点。 首先,根据引用[1][3][5],Pascal VOC的标注通常是XML文件,但用户提到的是彩色标签图像,可能是指每个类别用不同颜色表示的掩码图。而SegmentationClassAug可能需要的是单通道灰度图,每个像素值对应类别索引。 接下来,我需要考虑如何将RGB颜色映射到灰度类别索引。Pascal VOC有预定义的颜色表,比如飞机是(128,0,0),对应的类别索引可能是0或1。需要确认颜色到索引的映射关系,可能参考VOC的colormap.txt文件。 然后,处理步骤应该是读取每个彩色图像,遍历每个像素,将RGB值映射到对应的类别索引,生成单通道灰度图。注意可能需要处理抗锯齿边缘的近似颜色,以及确保输出为8位PNG格式。 根据引用[2][3][5],用户可能需要Python脚本实现,使用PIL或OpenCV库。需要处理文件遍历、颜色匹配、换和保存。还要注意颜色映射的准确性,可能需要字典来存储颜色到类别的对应关系。 可能的问题包括颜色值的微小差异,比如抗锯齿导致的近似颜色,可能需要计算欧氏距离找到最接近的颜色。另外,确保输出目录存在,处理图像时的效率,以及验证换后的灰度值是否正确。 最后,根据用户提供的引用,特别是引用[2][3][5]中的换脚本示例,可以构建类似的逻辑,但针对颜色到灰度的换。可能需要用户提供颜色映射文件或手动定义颜色字典。 总结步骤:加载颜色映射,读取彩色图,换每个像素,保存为灰度图。需要示例代码和注意事项,如颜色容差和输出格式。</think>### 分步解决方案 1. **理解颜色映射关系** Pascal VOC 数据集中的彩色标签图像通常使用特定 RGB 颜色表示不同类别(例如:飞机对应 `(128, 0, 0)`,自行车对应 `(0, 128, 0)`)。SegmentationClassAug 的灰度图格式要求每个像素值为类别索引(如 0 表示背景,1 表示飞机等)。需要先定义颜色与索引的映射关系[^3][^5]。 2. **编写颜色到索引的换脚本** 使用 Python 的 `PIL` 或 `OpenCV` 库遍历每个像素,根据预设的颜色表将 RGB 值替换为对应的类别索引。 ```python from PIL import Image import numpy as np import os # 定义颜色到类别索引的映射(需根据实际标签颜色修改) color_map = { (0, 0, 0): 0, # 背景 (128, 0, 0): 1, # 类别1(例如飞机) (0, 128, 0): 2, # 类别2(例如自行车) # 添加更多颜色映射... } def convert_voc_to_grayscale(input_path, output_path): img = Image.open(input_path).convert("RGB") pixels = np.array(img) grayscale = np.zeros((pixels.shape[0], pixels.shape[1]), dtype=np.uint8) for color, idx in color_map.items(): # 匹配颜色并赋值索引 mask = (pixels == np.array(color)).all(axis=-1) grayscale[mask] = idx # 处理抗锯齿边缘(近似颜色匹配) for y in range(pixels.shape[0]): for x in range(pixels.shape[1]): if tuple(pixels[y, x]) not in color_map: min_dist = float('inf') matched_idx = 0 for c, i in color_map.items(): dist = np.sqrt(sum((pixels[y, x] - c)**2)) if dist < min_dist: min_dist = dist matched_idx = i grayscale[y, x] = matched_idx Image.fromarray(grayscale).save(output_path) # 批量处理 input_dir = "VOC_SegmentationClass" output_dir = "SegmentationClassAug" os.makedirs(output_dir, exist_ok=True) for filename in os.listdir(input_dir): if filename.endswith(".png"): input_path = os.path.join(input_dir, filename) output_path = os.path.join(output_dir, filename) convert_voc_to_grayscale(input_path, output_path) ``` 3. **注意事项** - **颜色映射准确性**:需确保 `color_map` 与实际标签颜色严格匹配,可通过 Pascal VOC 的 `colormap.txt` 文件获取官方颜色定义[^3]。 - **抗锯齿处理**:标签边缘可能存在颜色插值,需通过近似匹配逻辑处理(如欧氏距离最近邻)。 - **输出格式**:灰度图需保存为单通道 8 位 PNG 格式,以保证兼容性[^2]。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值