You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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()

关键说明

  1. 遮罩层实现:通过Canvas先绘制全屏半透明黑色矩形,再绘制中间完全透明的圆形(可改为方形等其他形状),实现中间显示相机画面、外围遮罩的效果,无需依赖透明图片
  2. 相机画面复用:只创建一个cam_label用于显示相机画面,每次打开相机时重新初始化,关闭时销毁,避免界面叠加
  3. 引用保留:将mask_canvas和cam_label作为全局变量,确保第二次打开相机时不会被垃圾回收,解决之前圆形不显示的问题
  4. 帧读取优化:添加ret判断,确保只有读取到有效帧时才更新画面,避免崩溃

内容的提问来源于stack exchange,提问作者Nas13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 19:17:33