一个简单的摄像头应用程序6
主要改进点:
使用 ThreadPoolExecutor 管理多线程:
使用 concurrent.futures.ThreadPoolExecutor 来管理多线程,这样可以更高效地处理图像。
在 main 函数中创建一个 ThreadPoolExecutor,并在每个循环中提交图像处理任务。
减少不必要的图像转换:
尽量减少图像格式之间的转换,以减少计算开销。
使用更高效的算法:
选择了更高效的滤波器和对焦方法,例如 cv2.fastNlMeansDenoisingColored 和 cv2.Laplacian。
import cv2
import os
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import datetime
import webbrowser
import logging
import concurrent.futures
import queue# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')# 检查并创建保存照片和视频的文件夹
def create_folder(folder_name):if not os.path.exists(folder_name):os.makedirs(folder_name)return folder_name# 获取文件夹中的最大编号
def get_next_file_number(folder_name, file_extension):files = os.listdir(folder_name)files = [f for f in files if f.endswith(file_extension)]if files:numbers = [int(f.split('.')[0]) for f in files]return max(numbers) + 1else:return 1# 将PIL图像转换为OpenCV图像
def pil_to_cv(image):return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)# 自动曝光调整
def auto_exposure(frame, exposure_value):frame = cv2.convertScaleAbs(frame, alpha=exposure_value, beta=0)return frame# 自动对焦
def auto_focus(frame):gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)laplacian = cv2.Laplacian(gray, cv2.CV_64F).var()if laplacian < 100: # 如果图像模糊,进行对焦frame = cv2.fastNlMeansDenoisingColored(frame, None, 10, 10, 7, 21)return frame# 鼠标回调函数
def mouse_callback(event, x, y, flags, param):global next_photo_number, next_video_number, running, recording, out, frame, scale_factor, cam_index, roi, button_hints, show_buttons, show_help, frame_width, frame_heightbutton_width = int(frame_width * 0.05) # 按钮区域宽度占界面宽度的1/20button_height = 40 # 每个按钮的高度if event == cv2.EVENT_LBUTTONDOWN:if 10 <= x <= button_width and 10 <= y <= 50: # 关闭按钮区域running = Falseelif 10 <= x <= button_width and 70 <= y <= 70 + button_height: # 拍照按钮区域threading.Thread(target=save_photo, args=(frame.copy(), next_photo_number)).start()next_photo_number += 1elif 10 <= x <= button_width and 120 <= y <= 120 + button_height: # 开始/停止录像按钮区域if not recording:start_recording()else:stop_recording()elif 10 <= x <= button_width and 170 <= y <= 170 + button_height: # 放大按钮区域scale_factor = min(3.0, scale_factor * 2)elif 10 <= x <= button_width and 220 <= y <= 220 + button_height: # 缩小按钮区域scale_factor = max(1.0, scale_factor / 2)elif 10 <= x <= button_width and 270 <= y <= 270 + button_height: # 切换摄像头按钮区域switch_camera()elif 10 <= x <= button_width and 320 <= y <= 320 + button_height: # 查看照片按钮区域open_photo_folder()elif 10 <= x <= button_width and 370 <= y <= 370 + button_height: # 查看视频按钮区域open_video_folder()elif 10 <= x <= button_width and 420 <= y <= 420 + button_height: # 去除噪点按钮区域apply_denoise()elif 10 <= x <= button_width and 470 <= y <= 470 + button_height: # 功能菜单按钮区域show_buttons = not show_buttonselif 10 <= x <= button_width and 520 <= y <= 520 + button_height: # 功能说明按钮区域show_help = not show_helpelif event == cv2.EVENT_RBUTTONDOWN:roi[0], roi[1] = x, yelif event == cv2.EVENT_RBUTTONUP:roi[2], roi[3] = x - roi[0], y - roi[1]if roi[2] < 0:roi[0] += roi[2]roi[2] = -roi[2]if roi[3] < 0:roi[1] += roi[3]roi[3] = -roi[3]elif event == cv2.EVENT_MOUSEMOVE:button_hints = []button_width = int(frame_width * 0.05) # 按钮区域宽度占界面宽度的1/20button_height = 40 # 每个按钮的高度if 10 <= x <= button_width and 10 <= y <= 50:button_hints.append("关闭")elif 10 <= x <= button_width and 70 <= y <= 70 + button_height:button_hints.append("拍照")elif 10 <= x <= button_width and 120 <= y <= 120 + button_height:button_hints.append("录像")elif 10 <= x <= button_width and 170 <= y <= 170 + button_height:button_hints.append("放大")elif 10 <= x <= button_width and 220 <= y <= 220 + button_height:button_hints.append("缩小")elif 10 <= x <= button_width and 270 <= y <= 270 + button_height:button_hints.append("切换摄像头")elif 10 <= x <= button_width and 320 <= y <= 320 + button_height:button_hints.append("查看照片")elif 10 <= x <= button_width and 370 <= y <= 370 + button_height:button_hints.append("查看视频")elif 10 <= x <= button_width and 420 <= y <= 420 + button_height:button_hints.append("去除噪点")elif 10 <= x <= button_width and 470 <= y <= 470 + button_height:button_hints.append("功能菜单")elif 10 <= x <= button_width and 520 <= y <= 520 + button_height:button_hints.append("功能说明")# 保存照片
def save_photo(frame, photo_number):file_path = os.path.join(photo_folder, f"{photo_number}.jpg")# 去除界面上的按钮clean_frame = remove_buttons(frame)# 裁剪区域clean_frame = clean_frame[roi[1]:roi[1] + roi[3], roi[0]:roi[0] + roi[2]]# 添加时间戳clean_frame = add_timestamp(clean_frame)try:cv2.imwrite(file_path, clean_frame)logging.info(f"照片已保存为 {file_path}")except Exception as e:logging.error(f"保存照片时出错: {e}")# 去除界面上的按钮
def remove_buttons(frame):pil_image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(pil_image)button_width = int(frame_width * 0.05) # 按钮区域宽度占界面宽度的1/20draw.rectangle((0, 0, button_width, frame_height), fill=(0, 0, 0, 0)) # 透明填充return pil_to_cv(pil_image)# 添加时间戳
def add_timestamp(image):now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(pil_image)draw.text((10, 10), now, font=font, fill=(255, 255, 255))return pil_to_cv(pil_image)# 开始录像
def start_recording():global out, recording, framefile_path = os.path.join(video_folder, f"{next_video_number}.mp4")fourcc = cv2.VideoWriter_fourcc(*'XVID')out = cv2.VideoWriter(file_path, fourcc, 20.0, (roi[2], roi[3]))recording = Truelogging.info(f"开始录像: {file_path}")# 停止录像
def stop_recording():global out, recordingtry:out.release()recording = Falselogging.info("录像已保存")except Exception as e:logging.error(f"停止录像时出错: {e}")# 切换摄像头
def switch_camera():global cap, cam_indexcap.release()cam_index = (cam_index + 1) % 2 # 切换到下一个摄像头cap = cv2.VideoCapture(cam_index)if not cap.isOpened():logging.error("无法打开摄像头")running = False# 打开照片文件夹
def open_photo_folder():folder_path = os.path.abspath(photo_folder)webbrowser.open(folder_path)# 打开视频文件夹
def open_video_folder():folder_path = os.path.abspath(video_folder)webbrowser.open(folder_path)# 应用去噪点滤镜
def apply_denoise():global filter_typefilter_type = "denoise"# 绘制按钮
def draw_buttons(pil_image, font, show_buttons):draw = ImageDraw.Draw(pil_image)button_width = int(frame_width * 0.05) # 按钮区域宽度占界面宽度的1/20button_height = 40 # 每个按钮的高度buttons = [((10, 10, button_width, 50), "关闭", (0, 0, 255)),((10, 70, button_width, 70 + button_height), "拍照", (255, 0, 0)),((10, 120, button_width, 120 + button_height), "录像", (0, 255, 0)),((10, 170, button_width, 170 + button_height), "放大", (0, 255, 255)),((10, 220, button_width, 220 + button_height), "缩小", (0, 255, 255)),((10, 270, button_width, 270 + button_height), "切换摄像头", (255, 255, 0)),((10, 320, button_width, 320 + button_height), "查看照片", (255, 165, 0)),((10, 370, button_width, 370 + button_height), "查看视频", (255, 165, 0)),((10, 420, button_width, 420 + button_height), "去除噪点", (255, 165, 0)),((10, 470, button_width, 470 + button_height), "功能菜单", (255, 165, 0)),((10, 520, button_width, 520 + button_height), "功能说明", (255, 165, 0))]for (x1, y1, x2, y2), text, color in buttons:if show_buttons or y1 >= 470:draw.rectangle((x1, y1, x2, y2), fill=color)draw.text((x1 + 10, y1 + 10), text, font=font, fill=(255, 255, 255))# 绘制提示信息
def draw_hints(pil_image, hints, font):draw = ImageDraw.Draw(pil_image)button_width = int(frame_width * 0.05) # 按钮区域宽度占界面宽度的1/20for i, hint in enumerate(hints):draw.text((10, frame_height - 10 - (i + 1) * 30), hint, font=font, fill=(0, 255, 0))# 绘制功能说明
def draw_help(pil_image, font):draw = ImageDraw.Draw(pil_image)help_text = ["功能说明:","q: 退出程序","g: 应用灰度滤镜","b: 应用模糊滤镜","r: 恢复原图","功能菜单: 显示/隐藏功能按钮","功能说明: 显示/隐藏功能说明"]for i, text in enumerate(help_text):draw.text((10, 10 + i * 30), text, font=font, fill=(255, 255, 255))# 应用图像滤镜
def apply_filter(image, filter_type):if filter_type == "grayscale":return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)elif filter_type == "blur":return cv2.GaussianBlur(image, (15, 15), 0)elif filter_type == "denoise":return cv2.fastNlMeansDenoisingColored(image, None, denoise_strength, 10, 7, 21)return image# 处理图像的线程
def process_frame(frame, exposure_value, filter_type, scale_factor, roi, result_queue):frame = auto_exposure(frame, exposure_value)frame = auto_focus(frame)frame = apply_filter(frame, filter_type)frame = cv2.resize(frame, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_LINEAR)frame_height, frame_width, _ = frame.shaperoi_x = max(0, min(roi[0], frame_width - 1))roi_y = max(0, min(roi[1], frame_height - 1))roi_width = max(0, min(roi[2], frame_width - roi_x))roi_height = max(0, min(roi[3], frame_height - roi_y))roi = [roi_x, roi_y, roi_width, roi_height]result_queue.put((frame, roi))# 主函数
def main():global running, frame, photo_folder, video_folder, next_photo_number, next_video_number, recording, out, scale_factor, cam_index, roi, button_hints, filter_type, cap, font, show_buttons, show_help, frame_width, frame_height, exposure_value, denoise_strengthphoto_folder = "photos"video_folder = "videos"create_folder(photo_folder)create_folder(video_folder)next_photo_number = get_next_file_number(photo_folder, '.jpg')next_video_number = get_next_file_number(video_folder, '.mp4')running = Truerecording = Falseout = Nonescale_factor = 1.0cam_index = 0button_hints = []filter_type = Noneshow_buttons = Falseshow_help = Falseexposure_value = 1.0denoise_strength = 10try:cap = cv2.VideoCapture(cam_index)if not cap.isOpened():raise RuntimeError("无法打开摄像头")except Exception as e:logging.error(f"错误: {e}")returncv2.namedWindow('摄像头')cv2.setMouseCallback('摄像头', mouse_callback)# 创建滑动条cv2.createTrackbar('曝光', '摄像头', 100, 200, lambda x: set_exposure(x / 100.0))cv2.createTrackbar('去噪强度', '摄像头', 10, 20, lambda x: set_denoise_strength(x))# 使用支持中文的字体文件font_path = "simhei.ttf" # 确保这个路径指向你的 simhei.ttf 文件font = ImageFont.truetype(font_path, 20)# 初始化ROI(Region of Interest)roi = [0, 0, 1920, 1080]result_queue = queue.Queue()with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:while running:ret, frame = cap.read()if not ret:logging.error("无法获取帧")break# 使用多线程处理图像future = executor.submit(process_frame, frame, exposure_value, filter_type, scale_factor, roi, result_queue)frame, roi = result_queue.get()# 获取当前帧的尺寸frame_height, frame_width, _ = frame.shape# 将OpenCV图像转换为PIL图像pil_image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))# 绘制按钮draw_buttons(pil_image, font, show_buttons)# 绘制ROI区域draw = ImageDraw.Draw(pil_image)draw.rectangle((roi[0], roi[1], roi[0] + roi[2], roi[1] + roi[3]), outline=(0, 255, 0), width=2)# 显示当前照片编号和缩放比例draw.text((10, 430), f"当前照片编号: {next_photo_number}", font=font, fill=(0, 255, 0))draw.text((10, 460), f"当前缩放比例: {scale_factor:.1f}x", font=font, fill=(0, 255, 0))# 绘制提示信息draw_hints(pil_image, button_hints, font)# 绘制功能说明if show_help:draw_help(pil_image, font)# 显示程序名称draw.text((10, 5), "天眼", font=font, fill=(255, 255, 255))# 将PIL图像转换回OpenCV图像frame = pil_to_cv(pil_image)cv2.imshow('摄像头', frame)key = cv2.waitKey(1) & 0xFFif key == ord('q'): # 按 'q' 键退出running = Falseelif key == ord('g'): # 按 'g' 键应用灰度滤镜filter_type = "grayscale"elif key == ord('b'): # 按 'b' 键应用模糊滤镜filter_type = "blur"elif key == ord('r'): # 按 'r' 键恢复原图filter_type = None# 检查窗口是否被关闭if cv2.getWindowProperty('摄像头', cv2.WND_PROP_VISIBLE) < 1:running = Falseif recording:out.write(frame[roi[1]:roi[1] + roi[3], roi[0]:roi[0] + roi[2]])if recording:stop_recording()cap.release()cv2.destroyAllWindows()# 设置曝光值
def set_exposure(value):global exposure_valueexposure_value = valuelogging.info(f"曝光值设置为: {exposure_value}")# 设置去噪点强度
def set_denoise_strength(value):global denoise_strengthdenoise_strength = valuelogging.info(f"去噪点强度设置为: {denoise_strength}")if __name__ == "__main__":main()