基于Python Tkinter设计带自定义边框的相机界面
解决方案:相机自定义边框与相机问题修复
核心问题修复点
- 相机重复创建Label:原代码每次调用
show_frames都会新建Label,导致界面叠加混乱,改为复用单个Label - Canvas遮罩引用丢失:之前的Canvas遮罩因未保留引用,第二次打开时被垃圾回收,现在将Canvas和遮罩元素作为全局变量保留
- 相机帧读取错误处理:原代码直接使用
cap.read()[1],未判断帧是否读取成功,添加ret判断避免崩溃 - 自定义边框实现:通过Canvas绘制半透明遮罩层,中间挖空显示相机画面,替代透明图片方案
修改后的完整代码
from tkinter import * from PIL import ImageTk, Image import cv2 from tkinter import PhotoImage main_root = Tk() main_root.withdraw() splash_root = Toplevel() splash_root.geometry("800x480") splash_root.overrideredirect(True) bg = PhotoImage(file="image/tc.png") lab = Label(splash_root, image=bg) lab.pack() def main(): splash_root.withdraw() root = Toplevel() root.geometry("800x480") root.resizable(width=False, height=False) root.title("") WIDTH = 800 HEIGHT = 480 cam_on = False cap = None cam_label = None # 复用的相机显示Label mask_canvas = None # 遮罩Canvas def first_window(): global cam_label, mask_canvas # 清理相机相关元素 if cam_label: cam_label.destroy() cam_label = None if mask_canvas: mask_canvas.destroy() mask_canvas = None frame = Frame(root, width=WIDTH, height=HEIGHT, bg="#edf5ef") frame.place(x=0, y=0) border1 = LabelFrame(root, bd=2, bg="black", fg="black", width=160, height=61) border1.place(x=80, y=300) newExamBtn = Button( root, text="New Exam", bg="#c1d4c6", font=("Helvetica", 13), padx=36, pady=15, border=0, command=newExam, ) newExamBtn.place(x=82, y=302) def newExam(): global cam_label, mask_canvas # 清理相机相关元素 if cam_label: cam_label.destroy() cam_label = None if mask_canvas: mask_canvas.destroy() mask_canvas = None frameNE = Frame(root, width=WIDTH, height=HEIGHT, bg="white") frameNE.place(x=0, y=0) backBtn = Button( root, bg="white", activebackground="white", border=0, width=64, height=64, image=backImgGray, command=first_window, ) backBtn.place(x=0, y=0) examBtnBorder = LabelFrame( frameNE, bd=2, bg="gray", fg="white", width=200, height=80 ) examBtnBorder.place(x=300, y=100) examBtnIcn = Label(root, bg="gray", image=examImg) examBtnIcn.place(x=310, y=105) examBtn = Button( frameNE, text="Exam", bg="gray", fg="white", activebackground="gray", activeforeground="white", border=0, font=("Helvetica", 20), command=Camera, ) examBtn.place(x=380, y=110) def start_vid(): global cam_on, cap, cam_label stop_vid() cam_on = True cap = cv2.VideoCapture(0) # 初始化相机显示Label,居中放置400x400的相机画面 cam_label = Label(root) cam_label.place(x=(WIDTH-400)//2, y=(HEIGHT-400)//2) show_frames() def stop_vid(): global cam_on, cap, cam_label cam_on = False if cap: cap.release() cap = None if cam_label: cam_label.destroy() cam_label = None def show_frames(): global cap, cam_on, cam_label if not cam_on or not cap: return ret, frame = cap.read() if ret: # 调整相机画面大小匹配中间挖空区域 cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = Image.fromarray(cv2image).resize((400, 400), Image.LANCZOS) imgtk = ImageTk.PhotoImage(image=img) cam_label.imgtk = imgtk cam_label.configure(image=imgtk) cam_label.after(10, show_frames) def closeCam_and_Back(): stop_vid() newExam() def Camera(): global mask_canvas frameC = Frame(root, width=WIDTH, height=HEIGHT, bg="#edf5ef") frameC.place(x=0, y=0) # 创建遮罩Canvas,覆盖整个窗口 mask_canvas = Canvas(root, width=WIDTH, height=HEIGHT, bg="#edf5ef", highlightthickness=0) mask_canvas.place(x=0, y=0) # 绘制自定义边框遮罩:外围半透明黑色,中间圆形挖空 mask_canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill="#00000080", outline="") # 中间圆形挖空区域,可根据需求调整半径或形状 center_x, center_y = WIDTH//2, HEIGHT//2 radius = 200 mask_canvas.create_oval(center_x-radius, center_y-radius, center_x+radius, center_y+radius, fill="#ffffff00", outline="#ffffff", width=4) start_vid() backBtn = Button( frameC, bg="#353b36", activebackground="#353b36", border=0, width=50, height=32, image=backImg32, command=closeCam_and_Back, ) backBtn.place(x=0, y=0) first_window() # 加载图片资源 backImg32 = Image.open("image/icons8-left-64.png").resize((32, 32), Image.LANCZOS) backImg32 = ImageTk.PhotoImage(backImg32) backImgGray = Image.open("image/icons8-left-64-gray.png").resize((50, 50), Image.LANCZOS) backImgGray = ImageTk.PhotoImage(backImgGray) examImg = PhotoImage(file="image/icons8-exam-64.png") splash_root.after(3000, main) mainloop()
关键说明
- 遮罩层实现:通过Canvas先绘制全屏半透明黑色矩形,再绘制中间完全透明的圆形(可改为方形等其他形状),实现中间显示相机画面、外围遮罩的效果,无需依赖透明图片
- 相机画面复用:只创建一个
cam_label用于显示相机画面,每次打开相机时重新初始化,关闭时销毁,避免界面叠加 - 引用保留:将
mask_canvas和cam_label作为全局变量,确保第二次打开相机时不会被垃圾回收,解决之前圆形不显示的问题 - 帧读取优化:添加
ret判断,确保只有读取到有效帧时才更新画面,避免崩溃
内容的提问来源于stack exchange,提问作者Nas13
相关产品推荐
相关产品推荐

