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,如何处理这种情况