cv2.VideoCapture() always return false

本文介绍了解决使用cv2.VideoCapture()读取摄像头视频时返回false的问题,指出这通常发生在通过pip安装的OpenCV版本低于3.4.1.15的情况下。解决方案包括自行编译OpenCV with ffmpeg或升级至更高版本的OpenCV。

背景:读取摄像头视频,cv2.VideoCapture() 总是返回false

解决办法:要么自己编译opencv with ffmpeg,要么安装高版本的opencv>=3.4.1.15 

之前是通过pip安装的opencv 3.2版本,测试的时候cv2.VideoCapture() 总是返回false。网上资料也都是让自己安装ffmpeg并编译opencv,再把.so文件copy到相应目录下。

直到 https://tutel.me/c/programming/questions/21792909/cv2videocaptureopen+always+returns+false

里解释了pip 安装的opencv 在3.4.1.15 版本之前是不支持视频模块的。

对于如何编译可以参考:

https://github.com/kratzert/Ubuntu_from_scratch/blob/master/Ubuntu_16_04LTS.md#installing-opencv3

import cv2 import numpy as np import onnxruntime as ort import time import RPi.GPIO as GPIO import numpy as np import serial import pyzbar.pyzbar as pyzbar def show(image): texts = pyzbar.decode(image) return texts def identify_color(frame): global c1 if frame is not None: ball_color1 = 'red1' ball_color2 = 'red2' ball_color3 = 'blue' ball_color4 = 'green' ball_color5 = 'grey1' ball_color6 = 'grey2' color_dist = {'red1': {'Lower': np.array([170, 99, 55]), 'Upper': np.array([255, 255, 169])}, 'blue': {'Lower': np.array([81, 105, 51]), 'Upper': np.array([133, 255, 170])}, 'green': {'Lower': np.array([36, 55, 47]), 'Upper': np.array([84, 255, 146])}, 'grey1': {'Lower': np.array([75, 30, 40]), 'Upper': np.array([185, 130, 255])}, 'grey2': {'Lower': np.array([0, 0, 80]), 'Upper': np.array([50, 25, 150])}} x1 = 0 x2 = 0 x3 = 0 c1 = '' start_row,start_col =130,190 end_row,end_col =700,540 frame=img0[start_row:end_row,start_col:end_col] cv2.imshow('frame',frame) gs_frame = cv2.GaussianBlur(frame, (9, 9), 0) # 高斯模糊 hsv = cv2.cvtColor(gs_frame, cv2.COLOR_BGR2HSV) # 转化成HSV图像 inRange_hsv1 = cv2.inRange(hsv, color_dist[ball_color1]['Lower'], color_dist[ball_color1]['Upper']) inRange_hsv2 = cv2.inRange(hsv, color_dist[ball_color3]['Lower'], color_dist[ball_color3]['Upper']) inRange_hsv3 = cv2.inRange(hsv, color_dist[ball_color4]['Lower'], color_dist[ball_color4]['Upper']) #erode_hsv = cv2.erode(hsv, None, iterations=1) # 腐蚀 粗的变细 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9)) Open1 = cv2.morphologyEx(inRange_hsv1, cv2.MORPH_OPEN, kernel) Open2 = cv2.morphologyEx(inRange_hsv2, cv2.MORPH_OPEN, kernel) Open3 = cv2.morphologyEx(inRange_hsv3, cv2.MORPH_OPEN, kernel) #cv2.imshow('Open',Open) # 5 Close 闭运算 Close1 = cv2.morphologyEx(Open1, cv2.MORPH_CLOSE, kernel) Close2 = cv2.morphologyEx(Open2, cv2.MORPH_CLOSE, kernel) Close3 = cv2.morphologyEx(Open3, cv2.MORPH_CLOSE, kernel) cv2.imshow('mask_red', Close1 ) cv2.imshow('mask_blue', Close2) cv2.imshow('mask_green', Close3) cnts1 = cv2.findContours(Close1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2] cnts2 = cv2.findContours(Close2.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2] cnts3 = cv2.findContours(Close3.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2] if cnts1 != '': for c in cnts1: area = abs(cv2.contourArea(c)) rect = cv2.minAreaRect(c) (x, y, w, h) = cv2.boundingRect(c) #print('red{},{}'.format(area,w / h)) if area > 1200 : #if w / h > 0.2 and w / h < 2.5: x1 = x #c = 'red' #print('x1:{}'.format(x1)) if cnts2 != '': for c in cnts2: area = abs(cv2.contourArea(c)) rect = cv2.minAreaRect(c) (x, y, w, h) = cv2.boundingRect(c) #print('blue{},{}'.format(area,w / h)) if area > 1200 : #if w / h > 0.2 and w / h < 2.5: x2 = x # c = 'blue' #print('x2:{}'.format(x2)) if cnts3 != '': for c in cnts3: area = abs(cv2.contourArea(c)) rect = cv2.minAreaRect(c) (x, y, w, h) = cv2.boundingRect(c) #print('green{},{}'.format(area,w / h)) if area > 1200: # if w / h > 0.2 and w / h < 2.5: x3 = x # c = 'green' #print('x3:{}'.format(x3)) if x1 != 0 and x2 == 0 and x3 == 0: #print('red') c1 = 'red' # serial_number(9) elif x1 == 0 and x2 != 0 and x3 == 0: #print('blue') c1 = 'blue' # serial_number(10) elif x1 == 0 and x2 == 0 and x3 != 0: #print('green') c1 = 'green' # serial_number(11) return c1 def plot_one_box(x, img, color=None, label=None, line_thickness=None): """ description: Plots one bounding box on image img, this function comes from YoLov5 project. param: x: a box likes [x1,y1,x2,y2] img: a opencv image object color: color to draw rectangle, such as (0,255,0) label: str line_thickness: int return: no return """ tl = ( line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 ) # line/font thickness color = color or [random.randint(0, 255) for _ in range(3)] c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3])) cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA) if label: tf = max(tl - 1, 1) # font thickness t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0] c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3 cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled cv2.putText( img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA, ) def _make_grid( nx, ny): xv, yv = np.meshgrid(np.arange(ny), np.arange(nx)) return np.stack((xv, yv), 2).reshape((-1, 2)).astype(np.float32) def cal_outputs(outs,nl,na,model_w,model_h,anchor_grid,stride): row_ind = 0 grid = [np.zeros(1)] * nl for i in range(nl): h, w = int(model_w/ stride[i]), int(model_h / stride[i]) length = int(na * h * w) if grid[i].shape[2:4] != (h, w): grid[i] = _make_grid(w, h) outs[row_ind:row_ind + length, 0:2] = (outs[row_ind:row_ind + length, 0:2] * 2. - 0.5 + np.tile( grid[i], (na, 1))) * int(stride[i]) outs[row_ind:row_ind + length, 2:4] = (outs[row_ind:row_ind + length, 2:4] * 2) ** 2 * np.repeat( anchor_grid[i], h * w, axis=0) row_ind += length return outs def post_process_opencv(outputs,model_h,model_w,img_h,img_w,thred_nms,thred_cond): conf = outputs[:,4].tolist() c_x = outputs[:,0]/model_w*img_w c_y = outputs[:,1]/model_h*img_h w = outputs[:,2]/model_w*img_w h = outputs[:,3]/model_h*img_h p_cls = outputs[:,5:] if len(p_cls.shape)==1: p_cls = np.expand_dims(p_cls,1) cls_id = np.argmax(p_cls,axis=1) p_x1 = np.expand_dims(c_x-w/2,-1) p_y1 = np.expand_dims(c_y-h/2,-1) p_x2 = np.expand_dims(c_x+w/2,-1) p_y2 = np.expand_dims(c_y+h/2,-1) areas = np.concatenate((p_x1,p_y1,p_x2,p_y2),axis=-1) areas = areas.tolist() ids = cv2.dnn.NMSBoxes(areas,conf,thred_cond,thred_nms) if len(ids)>0: return np.array(areas)[ids],np.array(conf)[ids],cls_id[ids] else: return [],[],[] def infer_img(img0,net,model_h,model_w,nl,na,stride,anchor_grid,thred_nms=0.4,thred_cond=0.5): # 图像预处理 img = cv2.resize(img0, [model_w,model_h], interpolation=cv2.INTER_AREA) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) / 255.0 blob = np.expand_dims(np.transpose(img, (2, 0, 1)), axis=0) # 模型推理 outs = net.run(None, {net.get_inputs()[0].name: blob})[0].squeeze(axis=0) # 输出坐标矫正 outs = cal_outputs(outs,nl,na,model_w,model_h,anchor_grid,stride) # 检测框计算 img_h,img_w,_ = np.shape(img0) boxes,confs,ids = post_process_opencv(outs,model_h,model_w,img_h,img_w,thred_nms,thred_cond) return boxes,confs,ids def serial_number(num): if num == 1: GPIO.output(GPIO1_OUT, GPIO.LOW) GPIO.output(GPIO4_OUT, GPIO.LOW) GPIO.output(GPIO5_OUT, GPIO.LOW) GPIO.output(GPIO7_OUT, GPIO.HIGH) elif num == 2: GPIO.output(GPIO1_OUT, GPIO.LOW) GPIO.output(GPIO4_OUT, GPIO.LOW) GPIO.output(GPIO5_OUT, GPIO.HIGH) GPIO.output(GPIO7_OUT, GPIO.LOW) elif num == 3: GPIO.output(GPIO1_OUT, GPIO.LOW) GPIO.output(GPIO4_OUT, GPIO.LOW) GPIO.output(GPIO5_OUT, GPIO.HIGH) GPIO.output(GPIO7_OUT, GPIO.HIGH) elif num == 4: GPIO.output(GPIO1_OUT, GPIO.LOW) GPIO.output(GPIO4_OUT, GPIO.HIGH) GPIO.output(GPIO5_OUT, GPIO.LOW) GPIO.output(GPIO7_OUT, GPIO.LOW) elif num == 5: GPIO.output(GPIO1_OUT, GPIO.LOW) GPIO.output(GPIO4_OUT, GPIO.HIGH) GPIO.output(GPIO5_OUT, GPIO.LOW) GPIO.output(GPIO7_OUT, GPIO.HIGH) elif num == 6: GPIO.output(GPIO1_OUT, GPIO.LOW) GPIO.output(GPIO4_OUT, GPIO.HIGH) GPIO.output(GPIO5_OUT, GPIO.HIGH) GPIO.output(GPIO7_OUT, GPIO.LOW) elif num == 7: GPIO.output(GPIO1_OUT, GPIO.LOW) GPIO.output(GPIO4_OUT, GPIO.HIGH) GPIO.output(GPIO5_OUT, GPIO.HIGH) GPIO.output(GPIO7_OUT, GPIO.HIGH) elif num == 8: GPIO.output(GPIO1_OUT, GPIO.HIGH) GPIO.output(GPIO4_OUT, GPIO.LOW) GPIO.output(GPIO5_OUT, GPIO.LOW) GPIO.output(GPIO7_OUT, GPIO.LOW) elif num == 9: GPIO.output(GPIO1_OUT, GPIO.HIGH) GPIO.output(GPIO4_OUT, GPIO.LOW) GPIO.output(GPIO5_OUT, GPIO.LOW) GPIO.output(GPIO7_OUT, GPIO.HIGH) elif num == 10: GPIO.output(GPIO1_OUT, GPIO.HIGH) GPIO.output(GPIO4_OUT, GPIO.LOW) GPIO.output(GPIO5_OUT, GPIO.HIGH) GPIO.output(GPIO7_OUT, GPIO.LOW) elif num == 11: GPIO.output(GPIO1_OUT, GPIO.HIGH) GPIO.output(GPIO4_OUT, GPIO.LOW) GPIO.output(GPIO5_OUT, GPIO.HIGH) GPIO.output(GPIO7_OUT, GPIO.HIGH) elif num == 12: GPIO.output(GPIO1_OUT, GPIO.HIGH) GPIO.output(GPIO4_OUT, GPIO.HIGH) GPIO.output(GPIO5_OUT, GPIO.LOW) GPIO.output(GPIO7_OUT, GPIO.LOW) elif num == 0: GPIO.output(GPIO1_OUT, GPIO.LOW) GPIO.output(GPIO4_OUT, GPIO.LOW) GPIO.output(GPIO5_OUT, GPIO.LOW) GPIO.output(GPIO7_OUT, GPIO.LOW) GPIO.output(GPIO29_led, GPIO.LOW) class VideoCapture: """Customized VideoCapture, always read latest frame """ def __init__(self, camera_id): # "camera_id" is a int type id or string name self.cap = cv2.VideoCapture(camera_id) self.q = queue.Queue(maxsize=3) self.stop_threads = False # to gracefully close sub-thread th = threading.Thread(target=self._reader) th.daemon = True # 设置工作线程为后台运行 th.start() # 实时读帧,只保存最后一帧 def _reader(self): while not self.stop_threads: ret, frame = self.cap.read() if not ret: break if not self.q.empty(): try: self.q.get_nowait() except queue.Empty: pass self.q.put(frame) def read(self): return self.q.get() def terminate(self): self.stop_threads = True self.cap.release() if __name__ == "__main__": GPIO0_IN = 11 # HW15 1 GPIO2_IN = 13 # HW8 2 GPIO3_IN = 15 # HW4 4 GPIO1_OUT = 12 # HW11 8 GPIO4_OUT = 16 # HW12 4 GPIO5_OUT = 18 # HW13 2 GPIO7_OUT = 22 # HW14 1 GPIO29_led=40 GPIO.setmode(GPIO.BOARD) GPIO.setwarnings(False) GPIO.setup(GPIO1_OUT, GPIO.OUT) GPIO.setup(GPIO4_OUT, GPIO.OUT) GPIO.setup(GPIO5_OUT, GPIO.OUT) GPIO.setup(GPIO7_OUT, GPIO.OUT) GPIO.setup(GPIO29_led, GPIO.OUT) GPIO.setup(GPIO0_IN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(GPIO2_IN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(GPIO3_IN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # 模型加载 model_pb_path = "best_y2.onnx" so = ort.SessionOptions() net = ort.InferenceSession(model_pb_path, so) # 标签字典 dic_labels= {0:'1', 1:'2', 2:'3', } # 模型参数 model_h = 320 model_w = 320 nl = 3 na = 3 stride=[8.,16.,32.] anchors = [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]] anchor_grid = np.asarray(anchors, dtype=np.float32).reshape(nl, -1, 2) video = 0 cap = cv2.VideoCapture(video) flag_det = True GPIO.output(GPIO29_led, GPIO.LOW) last_mode = 1 t = '' c = '' location=[0,0] color1 = [] color2 = [] new_c = '' d = [] k = 0 a = 0 jishu1 = 0 jishu2 = 0 jishu3 = 0 jishu=0 j=7 last_j=99 while True: success, img0 = cap.read() if success: if flag_det: height, width = img0.shape[:2] # mode=3 mode = GPIO.input(GPIO0_IN) * 1 + GPIO.input(GPIO2_IN) * 2 + GPIO.input(GPIO3_IN) * 0 # print(GPIO.input(GPIO0_IN),GPIO.input(GPIO2_IN),GPIO.input(GPIO3_IN)) print('mode:{}'.format(mode)) if mode >= 0 and mode < 4: cv2.imshow('camera', img0) if cv2.waitKey(1) & 0xFF==ord('q'): break if mode==0: serial_number(0) if mode==1: t1 = time.time() start_row,start_col =50,190 end_row,end_col =700,540 cropped_img0=img0[start_row:end_row,start_col:end_col] #cv2.imshow('vide1', cropped_img0) det_boxes,scores,ids = infer_img(cropped_img0,net,model_h,model_w,nl,na,stride,anchor_grid,thred_nms=0.3,thred_cond=0.4) t2 = time.time() for box,score,id in zip(det_boxes,scores,ids): #label = '%s:%.2f'%(dic_labels[id],score) if dic_labels[id]=='1' and score>0.4: j='1' elif dic_labels[id]=='2' and score>0.4: j='2' elif dic_labels[id]=='3' and score>0.4: j='3' label = '%s:%.2f'%(dic_labels[id],score) plot_one_box(box.astype(np.int16), cropped_img0, color=(255,0,0), label=label, line_thickness=None) if j==last_j: if j=='1': print(j) serial_number(1) print('1') last_j=99 elif j=='2': print(2) serial_number(2) print('2') last_j=99 elif j=='3': print(j) serial_number(3) print('3') last_j=99 last_j=j str_FPS = "FPS: %.2f"%(1./(t2-t1)) cv2.putText(cropped_img0,str_FPS,(50,50),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),3) cv2.imshow('vide0', cropped_img0) elif mode==2: t1 = time.time() det_boxes,scores,ids = infer_img(img0,net,model_h,model_w,nl,na,stride,anchor_grid,thred_nms=0.3,thred_cond=0.4) t2 = time.time() for box,score,id in zip(det_boxes,scores,ids): #label = '%s:%.2f'%(dic_labels[id],score) if dic_labels[id]=='1' and score>0.4: j='1' elif dic_labels[id]=='2' and score>0.4: j='2' elif dic_labels[id]=='3' and score>0.4: j='3' label = '%s:%.2f'%(dic_labels[id],score) plot_one_box(box.astype(np.int16), img0, color=(255,0,0), label=label, line_thickness=None) if j==last_j: if j=='1': print(j) serial_number(1) print('1') last_j=99 elif j=='2': print(2) serial_number(2) print('2') last_j=99 elif j=='3': print(j) serial_number(3) print('3') last_j=99 last_j=j str_FPS = "FPS: %.2f"%(1./(t2-t1)) cv2.putText(img0,str_FPS,(50,50),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),3) cv2.imshow('vide0', img0) elif mode == 4: # size = (int(width * 0.5), int(height * 0.5)) # img0 = cv2.resize(img0, size, interpolation=cv2.INTER_AREA) c = identify_color(img0) if c == 'red': d.append('red') jishu = jishu + 1 elif c == 'blue': d.append('blue') jishu = jishu + 1 elif c == 'green': d.append('green') jishu = jishu + 1 #print(jishu) if jishu == 3: print(d) for i in range(0, 3): if i > 0 and d[i] == d[i - 1]: k = k + 1 #print(k) if k == 2: if d[0] == 'red': serial_number(1) print('output_Red') elif d[0] == 'blue': serial_number(2) print('output_blue') else: d[0]='green' serial_number(3) print('output_green') jishu = 0 d = [] k = 0 elif mode == 3 : t1 = time.time() det_boxes,scores,ids = infer_img(img0,net,model_h,model_w,nl,na,stride,anchor_grid,thred_nms=0.3,thred_cond=0.4) t2 = time.time() detctions=list(zip(det_boxes,scores,ids)) for box,score,id in zip(det_boxes,scores,ids): label = '%s:%.2f'%(dic_labels[id],score) plot_one_box(box.astype(np.int16), img0, color=(255,0,0), label=label, line_thickness=None) if detctions!=color1: if len(detctions)== 2: a=detctions[0][0][0] b=detctions[1][0][0] if a<b : a1=detctions[0][2] b1=detctions[1][2] location[0]=a1+1 location[1]=b1+1 print(location) elif b<a : a1=detctions[0][2] b1=detctions[1][2] location[0]=b1+1 location[1]=a1+1 print(location) if location==[1,2]: print(1) serial_number(1) elif location==[1,3]: print(2) serial_number(2) elif location==[2,3]: print(3) serial_number(3) elif location==[2,1]: print(4) serial_number(4) elif location==[3,1]: print(5) serial_number(5) elif location==[3,2]: print(6) serial_number(6) str_FPS = "FPS: %.2f"%(1./(t2-t1)) cv2.putText(img0,str_FPS,(50,50),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),3) cv2.imshow('vide0', img0) 代码详解
08-25
import cv2 import numpy as np import os import tkinter as tk from tkinter import filedialog, ttk, messagebox from PIL import Image, ImageTk import re from collections import deque class FastImageBrowser: def __init__(self, root): self.root = root self.root.title("去噪图") self.root.geometry("1400x700") # 初始化变量 self.image_folder = "" self.image_files = [] self.current_index = 0 self.current_image = None self.processed_image = None self.is_playing = False self.play_delay = 100 # 默认轮播速度 self.play_timer = None self.scale_factor = 0.1 self.min_clip = tk.DoubleVar(value=0.0) self.max_clip = tk.DoubleVar(value=1.0) self.buffer_size = 3 self.frame_buffer = deque(maxlen=self.buffer_size) # 多帧缓冲 # 统一尺寸(默认为None,首次加载时确定) self.fixed_size = None # 参数 self.nlm_h = tk.IntVar(value=10) self.min_clip = tk.DoubleVar(value=0.0) self.max_clip = tk.DoubleVar(value=0.99) self.gamma_value = tk.DoubleVar(value=1.0) # 固定gamma值,不自动调节 self.setup_ui() def setup_ui(self): controls_frame = tk.Frame(self.root, padx=10, pady=5) controls_frame.pack(fill=tk.X) tk.Button(controls_frame, text="选择文件夹", command=self.select_folder).grid(row=0, column=0, padx=5) tk.Label(controls_frame, text="选择图片:").grid(row=0, column=1, padx=5) self.image_combobox = ttk.Combobox(controls_frame, state="readonly", width=30) self.image_combobox.grid(row=0, column=2, padx=5) self.image_combobox.bind("<<ComboboxSelected>>", self.on_image_selected) # 播放控制按钮 button_frame = tk.Frame(controls_frame) button_frame.grid(row=0, column=3, padx=5) self.play_button = tk.Button(button_frame, text="开始轮播", command=self.toggle_play) self.play_button.pack(side=tk.LEFT, padx=2) tk.Button(button_frame, text="上一张", command=self.prev_image).pack(side=tk.LEFT, padx=2) tk.Button(button_frame, text="下一张", command=self.next_image).pack(side=tk.LEFT, padx=2) # 轮播速度选择 tk.Label(controls_frame, text="轮播速度(ms):").grid(row=0, column=4, padx=5) self.speed_var = tk.IntVar(value=1000) speed_combobox = ttk.Combobox(controls_frame, textvariable=self.speed_var, width=8) speed_combobox.grid(row=0, column=5, padx=5) speed_combobox['values'] = (50,100,200,500,1000) speed_combobox.state(["readonly"]) speed_combobox.bind("<<ComboboxSelected>>", self.update_play_speed) # 图像缩放控制 tk.Label(controls_frame, text="图像缩放(%):").grid(row=0, column=6, padx=5) self.scale_var = tk.DoubleVar(value=100) scale_combo = ttk.Combobox(controls_frame, textvariable=self.scale_var, width=8) scale_combo.grid(row=0, column=7, padx=5) scale_combo['values'] = ("1", "5", "10", "20", "40", "60","80","100") scale_combo.state(["readonly"]) scale_combo.bind("<<ComboboxSelected>>", self.update_scale_factor) # 去噪参数控制 tk.Label(controls_frame, text="帧间去噪强度:").grid(row=0, column=8, padx=5) tk.Scale(controls_frame, from_=0, to=30, orient=tk.HORIZONTAL, variable=self.nlm_h, length=150, command=lambda e: self.process_image()).grid(row=0, column=9, padx=5) # 对比度拉伸阈值控制 threshold_frame = tk.Frame(self.root, padx=10, pady=5) threshold_frame.pack(fill=tk.X) # 最小值阈值滑块 (0%-50%) tk.Label(threshold_frame, text="最小值阈值(0-0.5):").pack(side=tk.LEFT, padx=5) min_scale = tk.Scale(threshold_frame, from_=0, to=0.5, resolution=0.01, orient=tk.HORIZONTAL, variable=self.min_clip, length=200, command=lambda e: self.process_image()) min_scale.pack(side=tk.LEFT, padx=5) # 最大值阈值滑块 (50%-100%) tk.Label(threshold_frame, text="最大值阈值(0.5-1):").pack(side=tk.LEFT, padx=5) max_scale = tk.Scale(threshold_frame, from_=0.5, to=1.0, resolution=0.01, orient=tk.HORIZONTAL, variable=self.max_clip, length=200, command=lambda e: self.process_image()) max_scale.pack(side=tk.LEFT, padx=5) # Gamma控制 gamma_frame = tk.Frame(self.root, padx=10, pady=5) gamma_frame.pack(fill=tk.X) tk.Label(gamma_frame, text="Gamma校正:").pack(side=tk.LEFT, padx=5) tk.Scale(gamma_frame, from_=0.1, to=3.0, resolution=0.1, orient=tk.HORIZONTAL, variable=self.gamma_value, length=300, command=lambda e: self.process_image()).pack(side=tk.LEFT) # 图像显示区域 images_frame = tk.Frame(self.root, padx=10, pady=5) images_frame.pack(fill=tk.BOTH, expand=True) for title, attr in [("原始图像", "canvas_original"), ("处理结果", "canvas_processed")]: frame = tk.Frame(images_frame) frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) tk.Label(frame, text=title).pack() canvas = tk.Canvas(frame, bg="lightgray", width=600, height=600) canvas.pack(fill=tk.BOTH, expand=True) setattr(self, attr, canvas) def update_play_speed(self, event=None): """更新轮播速度""" self.play_delay = self.speed_var.get() if self.is_playing: self.stop_play() self.toggle_play() def update_scale_factor(self, event=None): new_scale = float(self.scale_var.get()) / 100 if abs(self.scale_factor - new_scale) < 1e-5: return self.scale_factor = new_scale if self.image_files: filename = self.image_files[self.current_index] self.load_and_buffer_image(filename) # 重新处理和显示当前图像 self.process_image() self.display_image(self.current_image, self.canvas_original) def select_folder(self): folder = filedialog.askdirectory(title="选择图片文件夹") if not folder: return self.stop_play() self.image_folder = folder exts = ('.tif', '.jpg', '.jpeg', '.png', '.bmp') self.image_files = sorted([f for f in os.listdir(folder) if f.lower().endswith(exts)]) if not self.image_files: messagebox.showwarning("警告", "文件夹中没有支持格式的图片") return # 按数字自然排序(有数字的文件名) def natural_key(filename): match = re.search(r'(\d+)', filename) if match: return int(match.group(1)) return filename self.image_files.sort(key=natural_key) self.image_combobox['values'] = self.image_files self.image_combobox.current(0) self.current_index = 0 self.frame_buffer.clear() self.fixed_size = None # 重置尺寸 self.load_and_buffer_image(self.image_files[0]) self.display_image(self.current_image, self.canvas_original) def on_image_selected(self, event): selected = self.image_combobox.get() if selected: self.current_index = self.image_files.index(selected) self.load_and_buffer_image(selected) self.display_image(self.current_image, self.canvas_original) self.process_image() def load_and_buffer_image(self, filename): path = os.path.join(self.image_folder, filename) try: img_data = np.fromfile(path, dtype=np.uint8) img = cv2.imdecode(img_data, cv2.IMREAD_UNCHANGED) if img is None: raise ValueError("图像解码失败") except Exception as e: messagebox.showerror("错误", f"无法加载图像 {filename}:\n{e}") return # 应用缩放 if self.scale_factor != 1.0: h, w = img.shape[:2] new_w = max(10, int(w * self.scale_factor)) new_h = max(10, int(h * self.scale_factor)) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA) # 转灰度并归一化成uint8 gray = self.to_grayscale(img) if gray.dtype != np.uint8: gray = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX) gray = gray.astype(np.uint8) # 首张图确定统一尺寸 if self.fixed_size is None: self.fixed_size = gray.shape[::-1] # 统一尺寸,resize成fixed_size if (gray.shape[1], gray.shape[0]) != self.fixed_size: gray = cv2.resize(gray, self.fixed_size, interpolation=cv2.INTER_AREA) self.current_image = gray # 更新缓冲 self.frame_buffer.append(gray) # 缓冲不够补齐 while len(self.frame_buffer) < self.buffer_size: self.frame_buffer.append(gray) self.process_image() def to_grayscale(self, img): if len(img.shape) == 3: return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) return img def gamma_correction(self, image, gamma=1.0): if gamma <= 0: gamma = 1.0 inv_gamma = 1.0 / gamma table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in range(256)]).astype(np.uint8) if image.dtype != np.uint8: image = cv2.normalize(image, None, 0, 255, cv2.NORM_MINMAX) image = image.astype(np.uint8) return cv2.LUT(image, table) def contrast_stretch(self, image, min_clip, max_clip): max_range = 255 min_val = np.percentile(image, min_clip * 100) max_val = np.percentile(image, max_clip * 100) result = np.zeros_like(image, dtype=float) mask_low = image < min_val mask_high = image > max_val mask_mid = (image >= min_val) & (image <= max_val) result[mask_low] = 0 result[mask_high] = max_range if max_val > min_val: normalized = (image[mask_mid] - min_val) / (max_val - min_val) result[mask_mid] = normalized * max_range else: result[mask_mid] = max_range / 2 return result.clip(0, 255).astype(np.uint8) def process_image(self): if len(self.frame_buffer) < self.buffer_size: return frames = list(self.frame_buffer) frames_uint8 = [] for f in frames: if f.dtype != np.uint8: nf = cv2.normalize(f, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) frames_uint8.append(nf) else: frames_uint8.append(f) # 多帧去噪 denoised = cv2.fastNlMeansDenoisingMulti( frames_uint8, imgToDenoiseIndex=self.buffer_size // 2, temporalWindowSize=self.buffer_size, h=self.nlm_h.get(), templateWindowSize=3, searchWindowSize=11 ) # Gamma校正 gamma_corrected = self.gamma_correction(denoised, self.gamma_value.get()) # 亮度裁剪 + 对比度拉伸 stretched = self.contrast_stretch( gamma_corrected, self.min_clip.get(), self.max_clip.get() ) # 中值滤波,去噪平滑 filtered = cv2.medianBlur(stretched, 3) # CLAHE局部对比度增强 clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(4, 4)) enhanced = clahe.apply(filtered) # Otsu二值化,用增强后的图像 _, binary = cv2.threshold(enhanced, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 开运算去噪 kernel_open = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel_open) # 闭运算补洞 kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel_close) # 找最大轮廓 contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: messagebox.showwarning("警告", "未检测到任何轮廓") return largest_contour = max(contours, key=cv2.contourArea) # 生成掩膜 mask = np.zeros_like(stretched, dtype=np.uint8) cv2.drawContours(mask, [largest_contour], -1, 255, thickness=-1) # 连通域面积筛选 contours_mask, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) mask_filtered = np.zeros_like(mask) area_threshold = 5000 # 可根据目标大小调整 for cnt in contours_mask: if cv2.contourArea(cnt) > area_threshold: cv2.drawContours(mask_filtered, [cnt], -1, 255, thickness=-1) mask = mask_filtered kernel_dilate = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask = cv2.dilate(mask, kernel_dilate, iterations=1) # 合成结果,掩膜区域用对比度拉伸后的图像,掩膜外设为0 result = denoised.copy() result[mask == 255] = stretched[mask == 255] result[mask == 0] = 0 # 彩色显示,绘制白色轮廓线 result_color = cv2.cvtColor(result, cv2.COLOR_GRAY2BGR) cv2.drawContours(result_color, [largest_contour], -1, (255, 255, 255), 2) self.processed_image = result_color self.display_image(self.processed_image, self.canvas_processed) def display_image(self, image, canvas): img = image # 16位转换8位 if img.dtype == np.uint16: img = (img / 256).astype(np.uint8) if len(img.shape) == 2: display_img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) elif len(img.shape) == 3 and img.shape[2] == 3: display_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) else: gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) display_img = cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB) img_pil = Image.fromarray(display_img) canvas_width = canvas.winfo_width() or 600 canvas_height = canvas.winfo_height() or 600 img_width, img_height = img_pil.size ratio = min(canvas_width / img_width, canvas_height / img_height) if ratio < 1: new_size = (int(img_width * ratio), int(img_height * ratio)) img_pil = img_pil.resize(new_size, Image.LANCZOS) img_tk = ImageTk.PhotoImage(img_pil) canvas.delete("all") canvas.create_image(canvas_width // 2, canvas_height // 2, anchor=tk.CENTER, image=img_tk) canvas.image = img_tk def toggle_play(self): if not self.image_files: messagebox.showwarning("警告", "请先选择包含图片的文件夹") return if self.is_playing: self.stop_play() self.play_button.config(text="开始轮播") else: self.play_delay = self.speed_var.get() self.is_playing = True self.play_button.config(text="停止轮播") self.play_next() def stop_play(self): self.is_playing = False if self.play_timer: self.root.after_cancel(self.play_timer) self.play_timer = None def play_next(self): if not self.is_playing or not self.image_files: return self.current_index = (self.current_index + 1) % len(self.image_files) filename = self.image_files[self.current_index] self.image_combobox.set(filename) self.load_and_buffer_image(filename) self.display_image(self.current_image, self.canvas_original) self.process_image() self.play_timer = self.root.after(self.play_delay, self.play_next) def next_image(self): if not self.image_files: return self.current_index = (self.current_index + 1) % len(self.image_files) filename = self.image_files[self.current_index] self.image_combobox.set(filename) self.load_and_buffer_image(filename) self.display_image(self.current_image, self.canvas_original) self.process_image() def prev_image(self): if not self.image_files: return self.current_index = (self.current_index - 1) % len(self.image_files) filename = self.image_files[self.current_index] self.image_combobox.set(filename) self.load_and_buffer_image(filename) self.display_image(self.current_image, self.canvas_original) self.process_image() if __name__ == "__main__": root = tk.Tk() app = FastImageBrowser(root) root.mainloop() 怎么改
07-09
import tkinter as tk from tkinter import ttk, messagebox, filedialog from PIL import Image, ImageTk, ImageDraw, ImageFont import vlc import os import cv2 import json import time import threading import queue import random import subprocess import sqlite3 from datetime import datetime import pandas as pd import pymysql from threading import Thread, Event OUT_DIR = "./output" class EmployeeClockSystem: def __init__(self, root): self.root = root self.root.title("员工工牌识别打卡系统") self.root.geometry("1200x700") self.root.configure(bg="#f0f0f0") self.idle = Event() # 创建事件对象 self.idle.set() # 初始设置为空闲状态(允许采集) self.capture_thread = None self.cap = None # 设置输出目录 self.OUT_DIR = "./output" os.makedirs(self.OUT_DIR, exist_ok=True) # 创建样式 self.style = ttk.Style() self.style.configure("Title.TLabel", font=("微软雅黑", 18, "bold"), foreground="#2c3e50") self.style.configure("Subtitle.TLabel", font=("微软雅黑", 14), foreground="#34495e") self.style.configure("Info.TLabel", font=("微软雅黑", 12), foreground="#2c3e50") self.style.configure("Card.TFrame", background="#ffffff", borderwidth=1, relief="raised", padding=10) self.style.configure("Control.TFrame", background="#e0e0e0", borderwidth=1, relief="sunken", padding=10) # 主布局框架 - 使用PanedWindow实现可调整的分割 main_paned = tk.PanedWindow(root, orient=tk.HORIZONTAL, sashrelief=tk.RAISED, sashwidth=4) main_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 左侧视频区域 (50%) left_frame = ttk.Frame(main_paned) main_paned.add(left_frame, stretch="always") # 右侧员工信息区域 (50%) right_frame = ttk.Frame(main_paned) main_paned.add(right_frame, stretch="always") # 视频流标题 ttk.Label(left_frame, text="实时视频监控", style="Title.TLabel").pack(pady=(0, 10), anchor=tk.W, padx=10) # 视频显示区域 video_card = ttk.Frame(left_frame, style="Card.TFrame") video_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) self.video_container = ttk.Frame(video_card) self.video_container.pack(fill=tk.BOTH, expand=True) # 视频控制面板 control_frame = ttk.Frame(left_frame, style="Control.TFrame") control_frame.pack(fill=tk.X, padx=10, pady=(0, 10)) # URL输入框 ttk.Label(control_frame, text="RTSP地址:").pack(side=tk.LEFT, padx=(0, 5)) self.url_entry = ttk.Entry(control_frame, width=40) self.url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10)) self.url_entry.insert(0, "rtsp://192.168.1.101/stream1") # 连接按钮 self.connect_button = ttk.Button(control_frame, text="启动监控", command=self.toggle_stream, width=12) self.connect_button.pack(side=tk.LEFT, padx=(0, 5)) # 截图按钮 self.snapshot_button = ttk.Button(control_frame, text="抓拍", command=self.take_snapshot, width=8, state=tk.DISABLED) self.snapshot_button.pack(side=tk.LEFT) # 员工信息标题 ttk.Label(right_frame, text="员工信息识别", style="Title.TLabel").pack(pady=(0, 10), anchor=tk.W, padx=10) # 员工信息卡片 info_card = ttk.Frame(right_frame, style="Card.TFrame") info_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # 员工照片和基本信息 info_frame = ttk.Frame(info_card) info_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 左侧员工照片区域 avatar_frame = ttk.Frame(info_frame, width=180, height=200) avatar_frame.pack(side=tk.LEFT, padx=(0, 20), fill=tk.Y) self.avatar_label = ttk.Label(avatar_frame) self.avatar_label.pack(fill=tk.BOTH, expand=True) # 默认头像 self.update_avatar() # 右侧员工详细信息 detail_frame = ttk.Frame(info_frame) detail_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) ttk.Label(detail_frame, text="员工基本信息", style="Subtitle.TLabel").pack(anchor=tk.W, pady=(0, 10)) # 信息标签 - 使用Grid布局更精确控制 label_frame = ttk.Frame(detail_frame) label_frame.pack(fill=tk.X, pady=5) ttk.Label(label_frame, text="姓名:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=0, column=0, sticky="e", padx=5, pady=5) self.name_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel") self.name_value.grid(row=0, column=1, sticky="w", padx=5, pady=5) ttk.Label(label_frame, text="工号:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=1, column=0, sticky="e", padx=5, pady=5) self.id_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel") self.id_value.grid(row=1, column=1, sticky="w", padx=5, pady=5) ttk.Label(label_frame, text="部门:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=2, column=0, sticky="e", padx=5, pady=5) self.dept_value = ttk.Label(label_frame, text="", width=20, anchor=tk.W, style="Info.TLabel") self.dept_value.grid(row=2, column=1, sticky="w", padx=5, pady=5) # ttk.Label(label_frame, text="打卡状态:", width=8, anchor=tk.E, style="Info.TLabel").grid(row=4, column=0, sticky="e", padx=5, pady=5) # self.status_value = ttk.Label(label_frame, text="未识别", width=20, anchor=tk.W, style="Info.TLabel") # self.status_value.grid(row=4, column=1, sticky="w", padx=5, pady=5) # 打卡按钮 # 打卡按钮 button_frame = ttk.Frame(detail_frame) button_frame.pack(fill=tk.X, pady=10) self.clock_button = ttk.Button(button_frame, text="导出打卡EXCEL", command=self.export_attendance_to_excel, width=15) self.clock_button.pack(side=tk.RIGHT, padx=(0, 20)) self.search_button = ttk.Button(button_frame, text="查询", command=self.open_search_window, width=15) self.search_button.pack(side=tk.RIGHT, padx=(0, 30)) # 考勤记录标题 ttk.Label(right_frame, text="今日考勤记录", style="Title.TLabel").pack(pady=(10, 10), anchor=tk.W, padx=10) # 考勤记录表格 record_card = ttk.Frame(right_frame, style="Card.TFrame") record_card.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) # 创建表格 columns = ("time", "id", "name", "dept", "status") self.record_tree = ttk.Treeview(record_card, columns=columns, show="headings", height=8) # 设置列标题 self.record_tree.heading("time", text="时间", anchor=tk.W) self.record_tree.heading("id", text="工号", anchor=tk.W) self.record_tree.heading("name", text="姓名", anchor=tk.W) self.record_tree.heading("dept", text="部门", anchor=tk.W) self.record_tree.heading("status", text="状态", anchor=tk.W) # 设置列宽 self.record_tree.column("time", width=150, anchor=tk.W) self.record_tree.column("id", width=100, anchor=tk.W) self.record_tree.column("name", width=100, anchor=tk.W) self.record_tree.column("dept", width=120, anchor=tk.W) self.record_tree.column("status", width=80, anchor=tk.W) # 添加滚动条 scrollbar = ttk.Scrollbar(record_card, orient="vertical", command=self.record_tree.yview) self.record_tree.configure(yscrollcommand=scrollbar.set) # 布局 self.record_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 状态栏 self.status_var = tk.StringVar(value="系统就绪") status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X) # 初始化变量 self.stream_active = False self.instance = vlc.Instance("--no-xlib") self.player = self.instance.media_player_new() # 模拟员工数据库 self.employees = { "1001": {"name": "张明", "dept": "技术部", "position": "高级工程师", "avatar": "avatar1.jpg"}, "1002": {"name": "李华", "dept": "市场部", "position": "市场经理", "avatar": "avatar2.jpg"}, "1003": {"name": "王芳", "dept": "财务部", "position": "会计", "avatar": "avatar3.jpg"}, "1004": {"name": "赵刚", "dept": "人力资源", "position": "招聘主管", "avatar": "avatar4.jpg"}, "1005": {"name": "陈晓", "dept": "产品部", "position": "产品经理", "avatar": "avatar5.jpg"}, } # 考勤记录 self.attendance_records = [] self.add_sample_records() # 初始化线程队列 self.gui_queue = queue.Queue() self.root.after(100, self.process_queue) def process_queue(self): """处理队列中的GUI更新任务""" while not self.gui_queue.empty(): try: task = self.gui_queue.get_nowait() if task["type"] == "update_employee_info": self.name_value.config(text=task["name"]) self.id_value.config(text=task["id"]) self.dept_value.config(text=task["dept"]) self.position_value.config(text=task["position"]) self.status_value.config(text=task["status"]) self.show_employee_avatar(task["avatar"]) self.clock_button.config(state=tk.NORMAL) elif task["type"] == "update_status": self.status_var.set(task["message"]) elif task["type"] == "clock_in": self.clock_in_task(task["emp_id"], task["emp_name"], task["emp_dept"]) except queue.Empty: pass self.root.after(100, self.process_queue) def update_avatar(self, image_path=None): if image_path and os.path.exists(image_path): try: # 尝试加载图片 print("opennnnnnnnnnnn") img = Image.open(image_path) print("opennnnnnnnnnnn") # 调整图片大小以适应显示区域 img = self.resize_image(img, target_width=180, target_height=200) # 转换为Tkinter PhotoImage photo = ImageTk.PhotoImage(img) # 更新显示 self.avatar_label.config(image=photo) self.avatar_label.image = photo # 保持引用 print(f"成功加载头像: {image_path}") return True except Exception as e: print(f"加载图片失败: {e}") # 加载失败时显示默认头像 self.show_default_avatar(error=True) return False else: # 没有提供图片路径或路径无效 self.show_default_avatar() return False def show_default_avatar(self): """显示默认头像""" default_img = Image.new('RGB', (180, 200), color='#3498db') draw = ImageDraw.Draw(default_img) try: font = ImageFont.truetype("arial.ttf", 24) except: font = ImageFont.load_default() draw.text((40, 85), "无数据", fill="white", font=font) default_photo = ImageTk.PhotoImage(default_img) self.avatar_label.config(image=default_photo) self.avatar_label.image = default_photo def resize_image(self, img, target_width, target_height): # """调整图片大小,保持宽高比并填充背景""" # 计算缩放比例 width, height = img.size scale = min(target_width / width, target_height / height) # 计算新尺寸 new_width = int(width * scale) new_height = int(height * scale) # 调整图片大小 img = img.resize((new_width, new_height), Image.LANCZOS) # 创建新图片并粘贴调整后的图片到中心 new_img = Image.new('RGB', (target_width, target_height), color='white') position = ( (target_width - new_width) // 2, (target_height - new_height) // 2 ) new_img.paste(img, position) return new_img def open_search_window(self): # """打开查找员工记录的弹窗""" search_window = tk.Toplevel(self.root) search_window.title("查找员工考勤记录") search_window.geometry("600x400") search_window.resizable(False, False) # 输入框 input_frame = ttk.Frame(search_window) input_frame.pack(pady=10, padx=10, fill=tk.X) ttk.Label(input_frame, text="姓名或工号:", width=12, anchor=tk.E).pack(side=tk.LEFT) search_entry = ttk.Entry(input_frame, width=30) search_entry.pack(side=tk.LEFT, padx=5) # 查询按钮 search_btn = ttk.Button(input_frame, text="查询", command=lambda: self.perform_search(search_entry, tree)) search_btn.pack(side=tk.LEFT) # 表格 tree_frame = ttk.Frame(search_window) tree_frame.pack(padx=10, pady=5, fill=tk.BOTH, expand=True) tree = ttk.Treeview(tree_frame, columns=("工号", "姓名", "部门","时间", "次数"), show="headings", height=15) tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 设置列标题 for col in tree["columns"]: tree.heading(col, text=col) tree.column(col, width=100, anchor=tk.CENTER) # 滚动条 scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=tree.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) tree.configure(yscrollcommand=scrollbar.set) # 提示信息 self.search_status_label = ttk.Label(search_window, text="", foreground="red") self.search_status_label.pack(pady=5) # 保存状态 self.current_search_tree = tree def perform_search(self, entry_widget, tree_widget): keyword = entry_widget.get().strip() if not keyword: print("//////////////") self.search_status_label.config(text="请输入姓名或工号") return try: conn = pymysql.connect( host='localhost', user='root', password='139800', database='employeeinfomation' ) print("**************") cursor = conn.cursor() print("**************") query = """ SELECT * FROM workcard WHERE NAME LIKE %s """ cursor.execute(query, (f"%{keyword}%")) results = cursor.fetchall() print(results) # 清空表格 for item in tree_widget.get_children(): tree_widget.delete(item) # 插入查询结果 for record in results: tree_widget.insert("", tk.END, values=record) if not results: self.search_status_label.config(text=f"未找到包含“{keyword}”的记录") else: self.search_status_label.config(text=f"找到 {len(results)} 条记录") conn.close() except Exception as e: self.search_status_label.config(text=f"查询失败:{str(e)}") def add_sample_records(self): """添加示例考勤记录""" records = [ ("09:00:24", "1001", "张明", "技术部", "正常"), ("09:01:35", "1002", "李华", "市场部", "迟到"), ("09:05:47", "1003", "王芳", "财务部", "正常"), ("12:01:15", "1001", "张明", "技术部", "外出"), ("13:30:08", "1004", "赵刚", "人力资源", "正常"), ] for record in records: self.attendance_records.append(record) self.record_tree.insert("", tk.END, values=record) def add_new_records(self, record): if (record.get('id') and record.get('name') and record.get('dept')): new_record = ( datetime.now().strftime("%H:%M:%S"), # 当前时间 record.get('id'), # 员工ID record.get('name'), # 姓名 record.get('dept'), # 部门 "正常" # 考勤状态 ) self.attendance_records.append(new_record) self.record_tree.insert("", tk.END, values=new_record) else: # 可选:添加错误处理(如日志记录或弹窗提示) print("错误:缺少必要的字段信息,记录未添加") def export_attendance_to_excel(self): # """从本地数据库导出考勤记录到Excel文件""" # 弹出文件保存路径 file_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")], title="保存考勤记录为Excel" ) if not file_path: return # 用户取消了保存 try: # 1. 连接 MySQL 数据库(根据你的实际配置填写) # conn = mysql.connector.connect( # host="localhost", # 数据库地址 # user="root", # 用户名 # password="yourpass", # 密码 # database="attendance_db" # 数据库名称 # ) conn = pymysql.connect( host='localhost', user='root', password='139800', database='employeeinfomation' ) # 2. 使用 pandas 直接读取 SQL 查询结果 query = "SELECT * FROM workcard" df = pd.read_sql_query(query, conn) # 3. 关闭数据库连接 conn.close() # 4. 如果没有数据 if df.empty: messagebox.showwarning("导出失败", "数据库中没有可导出的考勤记录!") return # 5. 导出为 Excel 文件 df.to_excel(file_path, index=False) messagebox.showinfo("导出成功", f"考勤记录已成功导出到:\n{file_path}") except Exception as e: messagebox.showerror("导出失败", f"导出考勤记录时发生错误:\n{str(e)}") def search_count(self, name): conn = pymysql.connect( host='localhost', user='root', password='139800', database='employeeinfomation' ) # 2. 使用 pandas 直接读取 SQL 查询结果 cursor = conn.cursor() query = "SELECT COUNT FROM workcard WHERE NAME LIKE %s " cursor.execute(query, (f"%{name}%")) count = cursor.fetchall() # 3. 关闭数据库连接 conn.close() return count def toggle_stream(self): """切换视频流状态""" if self.stream_active: self.stop_stream() else: self.start_stream() def start_stream(self): """启动视频流""" url = self.url_entry.get().strip() if not url: messagebox.showerror("错误", "请输入有效的视频流URL") return try: media = self.instance.media_new(url) self.player.set_media(media) win_id = self.video_container.winfo_id() if os.name == 'nt': win_id = int(win_id) self.player.set_hwnd(win_id) else: self.player.set_xwindow(win_id) self.player.play() self.stream_active = True self.connect_button.config(text="停止监控") self.snapshot_button.config(state=tk.NORMAL) self.status_var.set(f"正在播放: {url}") # 启动视频流线程 threading.Thread(target=self.video_thread, daemon=True).start() # 启动识别线程 # threading.Thread(target=self.recognition_thread, daemon=True).start() # 启动采集线程 self.capture_thread = Thread(target=self.capture_loop, daemon=True) self.capture_thread.start() except Exception as e: messagebox.showerror("连接错误", f"无法连接到视频流: {str(e)}") self.status_var.set("连接失败") def video_thread(self): """视频流播放线程""" pass # 视频流由VLC内部处理,无需额外操作 # def recognition_thread(self, out_dir: str = OUT_DIR): # """使用实际工牌识别代码进行识别的线程""" # cap = cv2.VideoCapture(self.url_entry.get().strip()) # if not cap.isOpened(): # print("[错误] 无法打开视频流") # return # # 加载预训练的人脸检测模型 # face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') # while self.stream_active: # ret, frame = cap.read() # if not ret: # print("[调试信息] 无法读取视频帧") # continue # # 转为灰度图,提高检测效率 # gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # faces = 0 # # 检测人脸 # faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30)) # if len(faces) > 0: # print(f"[调试信息] 检测到 {len(faces)} 张人脸") # # 保存当前帧供后续处理 # temp_image_path = "temp_frame.jpg" # cv2.imwrite(temp_image_path, frame) # print(f"[调试信息] 图像已保存至 {temp_image_path}") # # 调用main.py 进行图像识别 # try: # print(f"[调试信息] 正在调用 main.py 处理 {temp_image_path}") # subprocess.run(["python", "main.py", temp_image_path], check=True) # print("[调试信息] main.py 执行完成") # except subprocess.CalledProcessError as e: # print(f"[错误信息] main.py 执行失败: {e}") # # 读取main.py 输出的JSON文件 # stem = os.path.splitext(os.path.basename(temp_image_path))[0] # final_json = os.path.join(OUT_DIR, f"{stem}_face_result.json") # final_picture = os.path.join(OUT_DIR, f"{stem}_face.jpg") # img = Image.open(final_picture) # print(img) # self.update_avatar(final_picture) # if os.path.exists(final_json): # print(f"[调试信息] JSON文件已找到: {final_json}") # with open(final_json, "r", encoding="utf-8") as f: # result = json.load(f) # ocr_info = result.get("OCR", {}) # emp_id = ocr_info.get("id") # emp_name = ocr_info.get("name") # emp_dept = ocr_info.get("department") # # 打印识别结果 # print(f"[调试信息] 识别结果: ID={emp_id}, 姓名={emp_name}, 部门={emp_dept}") # # 将识别结果发送到GUI队列 # task = { # "type": "update_employee_info", # "name": emp_name, # "id": emp_id, # "dept": emp_dept, # "position": "未知", # "avatar": os.path.join(OUT_DIR, f"{stem}_face.jpg") # } # self.gui_queue.put(task) # # 添加示例记录 # count = self.search_count(emp_name) # if(count == 1): # self.add_new_records(task) # messagebox.showinfo("打卡成功", f"{emp_name} 已成功打卡!") # else: # messagebox.showinfo("Warnning!", f"{emp_name} 重复打卡!") # # time.sleep(10) # 控制识别频率 # cap.release() def capture_loop(self): # """视频采集主循环,使用事件控制采集频率""" self.cap = cv2.VideoCapture(self.url_entry.get().strip()) if not self.cap.isOpened(): print("[错误] 无法打开视频流") return # 加载预训练的人脸检测模型 face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') while self.stream_active: # 等待空闲状态(允许采集) self.idle.wait() # time.sleep(5) # 读取一帧 ret, frame = self.cap.read() if not ret: print("[调试信息] 无法读取视频帧") time.sleep(0.1) continue # 转为灰度图,提高检测效率 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 检测人脸 faces = face_cascade.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30) ) if len(faces) > 0: print(f"[调试信息] 检测到 {len(faces)} 张人脸") # 立即设置为忙碌状态(禁止进一步采集) self.idle.clear() # 启动处理线程 Thread( target=self.process_frame, args=(frame.copy(),), # 复制帧以避免后续修改 daemon=True ).start() else: # 未检测到人脸时短暂休眠,控制抽帧频率 time.sleep(0.05) def process_frame(self, frame): # """处理检测到人脸的帧""" try: # 保存当前帧 temp_image_path = "temp_frame.jpg" cv2.imwrite(temp_image_path, frame) print(f"[调试信息] 图像已保存至 {temp_image_path}") # 调用main.py进行图像识别 print(f"[调试信息] 正在调用 main.py 处理 {temp_image_path}") subprocess.run(["python", "main.py", temp_image_path], check=True) print("[调试信息] main.py 执行完成") # 读取main.py输出的JSON文件 stem = os.path.splitext(os.path.basename(temp_image_path))[0] final_json = os.path.join(OUT_DIR, f"{stem}_face_result.json") final_picture = os.path.join(OUT_DIR, f"{stem}_face.jpg") if os.path.exists(final_json): print(f"[调试信息] JSON文件已找到: {final_json}") with open(final_json, "r", encoding="utf-8") as f: result = json.load(f) ocr_info = result.get("OCR", {}) emp_id = ocr_info.get("id") emp_name = ocr_info.get("name") emp_dept = ocr_info.get("department") # 打印识别结果 print(f"[调试信息] 识别结果: ID={emp_id}, 姓名={emp_name}, 部门={emp_dept}") # 更新GUI img = Image.open(final_picture) self.update_avatar(final_picture) # 将识别结果发送到GUI队列 task = { "type": "update_employee_info", "name": emp_name, "id": emp_id, "dept": emp_dept, "position": "未知", "avatar": final_picture } self.gui_queue.put(task) # 检查打卡记录 count = self.search_count(emp_name) if count == 0: self.add_new_records(task) messagebox.showinfo("打卡成功", f"{emp_name} 已成功打卡!") else: messagebox.showinfo("警告", f"{emp_name} 重复打卡!") else: print(f"[错误] 未找到JSON文件: {final_json}") # 处理失败时也恢复采集 except subprocess.CalledProcessError as e: print(f"[错误] main.py 执行失败: {e}") except Exception as e: print(f"[错误] 处理过程中发生异常: {str(e)}") finally: # 无论处理成功与否,都恢复空闲状态 self.idle.set() def stop(self): # """停止采集和处理""" self.stream_active = False self.idle.set() # 确保线程可以退出 if self.cap and self.cap.isOpened(): self.cap.release() def stop_stream(self): """停止视频流""" if self.player: self.player.stop() self.stream_active = False self.connect_button.config(text="启动监控") self.snapshot_button.config(state=tk.DISABLED) self.clock_button.config(state=tk.DISABLED) self.status_var.set("已停止视频流") # 清空员工信息 self.name_value.config(text="") self.id_value.config(text="") self.dept_value.config(text="") self.position_value.config(text="") self.status_value.config(text="未识别") self.show_default_avatar() def take_snapshot(self): """抓拍当前帧""" if self.stream_active: timestamp = time.strftime("%Y%m%d_%H%M%S") filename = f"snapshot_{timestamp}.png" self.player.video_take_snapshot(0, filename, 0, 0) messagebox.showinfo("抓拍成功", f"已保存截图: {filename}") self.status_var.set(f"截图已保存: {filename}") def show_employee_avatar(self, avatar_path): """显示员工头像""" try: colors = ["#3498db", "#2ecc71", "#e74c3c", "#f39c12", "#9b59b6"] color = random.choice(colors) img = Image.new('RGB', (180, 200), color=color) draw = ImageDraw.Draw(img) try: font = ImageFont.truetype("arial.ttf", 20) except: font = ImageFont.load_default() draw.text((40, 85), "员工照片", fill="white", font=font) photo = ImageTk.PhotoImage(img) self.avatar_label.config(image=photo) self.avatar_label.image = photo except Exception as e: print(f"头像加载错误: {e}") def clock_in(self): """员工打卡""" emp_id = self.id_value.cget("text") emp_name = self.name_value.cget("text") emp_dept = self.dept_value.cget("text") task = {"type": "clock_in", "emp_id": emp_id, "emp_name": emp_name, "emp_dept": emp_dept} self.gui_queue.put(task) def clock_in_task(self, emp_id, emp_name, emp_dept): """执行打卡逻辑""" current_time = time.strftime("%H:%M:%S") hour = int(time.strftime("%H")) minute = int(time.strftime("%M")) status = "迟到" if (hour > 9 or (hour == 9 and minute > 0)) else "正常" record = (current_time, emp_id, emp_name, emp_dept, status) self.attendance_records.append(record) self.record_tree.insert("", tk.END, values=record) self.status_value.config(text=f"已打卡 ({status})") self.status_var.set(f"{emp_name} 打卡成功! 时间: {current_time}") self.clock_button.config(state=tk.DISABLED) self.record_tree.see(self.record_tree.get_children()[-1]) if __name__ == "__main__": root = tk.Tk() app = EmployeeClockSystem(root) root.mainloop() 为什么使用了event还是一直截取同一个时刻的照片,而不是隔一段时间再截图
最新发布
08-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值