一个使用了AdelaiDet和detectron2实现的人物分割系统

因为AdelaiDet和detectron2这两个库都是基于Linux系统的,要改在win上运行老费事了,网上虽然有一点教程,但有很多零碎修改的地方,比如将Linux才有的系统文件改为win上的。这里就直接贴几个修改教程网址,

Win10编译Detectron2和TensorMask - 知乎

Windows安装AdelaiDet的血与泪_adelaidet教程-CSDN博客

windows10+detectron2完美安装教程_detectron2安装-CSDN博客

基于Detectron2的BlendMask训练 BlendMask环境配置 COCO数据集_no module named 'detectron2-CSDN博客

自己要能运行它的demo就问题不大。

数据集

使用COCO2017数据集,但只保留人的图片和人实例,用下面代码来对数据集进行类别过滤。:

# 这个文件是用来从原始的COCO数据集中提取出仅关于“人”的标注
# 就是生成训练集,测试集用的,正式使用时不会用到。

# ps:后来将=anaconda3环境名称从detectron2改为了Ai_AdelaiDet,但那时已经生成过数据集了,所以就没改
import json
import os
import shutil
from pycocotools.coco import COCO
import time


start = time.perf_counter()  # 记录开始时间
# 输入
# 加载COCO数据集的标注
coco_root = 'C:/Users/86135/anaconda3/envs/detectron2/Lib/site-packages/AdelaiDet-master/datasets/coco/'  # 这是我解压的数据集路径
dataType = 'train2017'  # 训练集
# dataType = 'val2017'  # 验证集
# 拼接出文件路径,annotations是标注文件夹名,
annotations_input = os.path.join(coco_root, 'annotations', 'instances_{}.json'.format(dataType))

# 输出
# 设置输出目录
output_dir = 'C:/Users/86135/anaconda3/envs/detectron2/Lib/site-packages/AdelaiDet-master/datasets/coco_person/'
annotations_output = os.path.join(output_dir, 'annotations', 'instances_{}.json'.format(dataType))
images_output = os.path.join(output_dir, dataType)  # 就不变文件名字了
# 确保输出目录存在
os.makedirs(os.path.join(output_dir, 'annotations'), exist_ok=True)
os.makedirs(images_output, exist_ok=True)

# 开始
images_input = os.path.join(coco_root, dataType)  # 图片
coco = COCO(annotations_input)  # 创建COCO数据集实例
filtered_annotations = {'images': [], 'annotations': [], 'categories': coco.dataset['categories']}

# 遍历所有图片
for img_id in coco.imgs.keys():  # 训练集 18G   测试集6G,我的电脑训练集要用3min左右,耐心一点处理后训练集大概10G,留足空间
    img_info = coco.loadImgs([img_id])[0]  # 图片信息字典
    img_file_name = img_info['file_name']  # 图片名
    # 加载图片的所有标注
    ann_ids = coco.getAnnIds(imgIds=img_id, iscrowd=None)
    anns = coco.loadAnns(ann_ids)  # 获得图片标注列表
    # 过滤出只包含人的标注
    person_anns = [ann for ann in anns if ann['category_id'] == 1]  # 类别ID为1的是人
    if person_anns:  # 如果这张图片有人
        shutil.copy2(os.path.join(images_input, img_file_name), images_output)  # 复制图像文件
        # 更新标注
        filtered_annotations['images'].append(img_info)
        filtered_annotations['annotations'].extend(person_anns)  # 添加人的标注

    # 将过滤后的标注保存为新的JSON文件
with open(annotations_output, 'w') as f:
    json.dump(filtered_annotations, f)

print(f"标注已保存到 {annotations_output}")
print(f"图片已保存到 {images_output}")
end = time.perf_counter()  # 记录结束时间
execution_time = end - start  # 计算运行时间(单位为秒)
print(f"总用时:{execution_time}s")

数据集训练

按README.md文件里说的操作就行。这里用BLendMask模型,因为他又快又准。‘】https://github.com/aim-uofa/AdelaiDet/blob/master/datasets/README.md#blendmask-instance-detection

先使用这两个文件生成所需的注释文件

然后自己仿照他的配置文件写一个自己的训练计划

_BASE_: "DLA_34_syncbn_4x.yaml"
SOLVER:
  IMS_PER_BATCH: 4
  STEPS: (100000, 110000)
  MAX_ITER: 120000
OUTPUT_DIR: "output/blendmask/My_BlendMask"

最后用train_net训练就行

这边在自己的笔记本RTX2060上训练了10.5h,最后AP39.0

系统

源码有注释,我挑点有意思的说

你在设置里可以修改其他实例分割模型,只要是基于detectron2的都行,给的压缩包里有俩,第一组是My_BlendMask.yaml和model_final.pth我训练的,第二组是R_101_dcni3_5x.yaml和R_101_dcni3_5x.pth,AdelaiDet demo里的。

视频处理左键点视频可以使用你系统的默认播放器播放,注意是系统的默认播放器,没设置就不清楚了,

我使用两个屏幕,这里的屏幕选择可以跨屏幕选择区域,仿照qq截屏的想法,后来想起来用透明窗口采用录屏类似的操作,但已经写完了,就没改。

众所周知,CV2读网络摄像头会随着时间增长逐渐叠加延迟,所以我在系统内维护了一个缓冲区读图片。并借鉴了这个里面的ip摄像头

Python调用手机摄像头-CSDN博客

方便你们不想下载文件的借鉴这里粘贴一下系统的代码。

Load_My_model.py

# 这里没有使用onnx模型,而是pth模型进行处理
import time

import cv2
import torch
from detectron2.data import MetadataCatalog
from detectron2.engine.defaults import DefaultPredictor
from detectron2.utils.video_visualizer import VideoVisualizer
from detectron2.utils.visualizer import ColorMode, Visualizer


class Visualization(object):  # 可视化组件,用这个进行模型的处理
    def __init__(self, cfg):
        self.metadata = MetadataCatalog.get(
            cfg.DATASETS.TEST[0] if len(cfg.DATASETS.TEST) else "__unused"
        )  # 获取元数据
        self.cfg = cfg
        self.cpu_device = torch.device("cpu")  # 为了可视化,转到CPU显示
        self.instance_mode = ColorMode.IMAGE  # 设置颜色模式
        self.predictor = DefaultPredictor(cfg)  # 创建预测器

    def _frame_from_video(self, video):  # 从视频,摄像头获取帧
        while video.isOpened():
            success, frame = video.read()
            if success:
                yield frame
            else:
                break

    def _frame_from_screen(self, screen):  # 从屏幕获取的帧
        while True:
            success, frame = screen.next()
            if success:
                yield frame
            else:
                break

    def run_model_image(self, image, visualization=True):  # 图片处理,第三个变量是否输出可视化结果
        predictions = self.predictor(image)
        if visualization:
            visualizer = Visualizer(image, self.metadata, instance_mode=self.instance_mode)  # 初始化可视器
            instances = predictions["instances"].to(self.cpu_device)
            vis_output = visualizer.draw_instance_predictions(predictions=instances)  # 使用实例分割可视化
            return predictions, vis_output  # 返回预测结果和可视化输出
        else:
            return predictions, []

    def run_model_video(self, video, visualization=True,screen = False):  # 视频处理,第三个变量是否输出可视化结果
        def process_predictions(frame, predictions):
            frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
            predictions = predictions["instances"].to(self.cpu_device)
            vis_frame = video_visualizer.draw_instance_predictions(frame, predictions)
            vis_frame = cv2.cvtColor(vis_frame.get_image(), cv2.COLOR_RGB2BGR)
            return vis_frame

        video_visualizer = VideoVisualizer(self.metadata, self.instance_mode)  # 初始化视频可视器
        if screen:
            frame_gen = self._frame_from_screen(video)
        else:
            frame_gen = self._frame_from_video(video)
        for frame in frame_gen:
            yield process_predictions(frame, self.predictor(frame))

Setup_cfg.py

# 设置配置文件,同时在直接运行时当demo使用(注意更改运行目录为项目根目录)
import time

from start_up.Load_My_model import Visualization
from adet.config import get_cfg
import tqdm  # 显示进度条
from detectron2.data.detection_utils import read_image
from mss import mss
import numpy as np
import cv2


def setup_cfg(path_pth, path_yaml, threshold=0.3):
    cfg = get_cfg()  # 设置配置
    cfg.merge_from_file(path_yaml)  # 配置文件
    cfg.merge_from_list(['MODEL.WEIGHTS', path_pth])  # 模型文件
    # cfg.merge_from_file("data/R_101_dcni3_5x.yaml")  # 配置文件
    # cfg.merge_from_list(['MODEL.WEIGHTS', "data/R_101_dcni3_5x.pth"])  # 模型文件
    cfg.MODEL.RETINANET.SCORE_THRESH_TEST = threshold  # 得分阈值
    cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = threshold
    cfg.MODEL.FCOS.INFERENCE_TH_TEST = threshold
    cfg.MODEL.MEInst.INFERENCE_TH_TEST = threshold
    cfg.MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH = threshold
    cfg.freeze()
    return cfg



if __name__ == "__main__":
    cfg = setup_cfg("data/model_final.pth", "data/My_BlendMask.yaml")
    visualization = Visualization(cfg)  # 初始化可视化器(内部加载过model)
    str = ["视频", "屏幕"]
    # str = ["视频", "屏幕"]
    if str[0] == "图片":
        img = read_image("data/test1.jpg", format="BGR")  # 换颜色模式
        predictions, visualized_output = visualization.run_model_image(img)
        cv2.namedWindow("COCO detections", cv2.WINDOW_NORMAL)  # 创建一个窗口,可手动调整大小
        cv2.imshow("COCO detections", visualized_output.get_image()[:, :, ::-1])
        while 1:
            if cv2.waitKey(0) == 27:
                break  # esc退出
    elif str[0] == "视频":  # 视频
        if str[1] == "文件":
            video = cv2.VideoCapture(
                "C:\\Users\\86135\\Videos\\Captures\\Ratopia 2024-03-13 19-10-32.mp4")
            for vis in tqdm.tqdm(visualization.run_model_video(video)):
                cv2.namedWindow("COCO detections", cv2.WINDOW_NORMAL)  # 创建一个窗口,可手动调整大小
                cv2.imshow("COCO detections", vis)  # 视频显示
                if cv2.waitKey(1) == 27:
                    break  # esc to quit
            video.release()  # 解除占用
        elif str[1] == "屏幕":
            # 没有摄像头,整个截屏吧
            sct = mss()  # 创建一个屏幕捕获对象
            width = 640
            height = 480
            scale_factor = 1  # 缩放系数
            new_width = int(width * scale_factor)
            new_height = int(height * scale_factor)
            monitor = {"top": 0, "left": 0, "width": width, "height": height}  # 定义捕获的屏幕区域
            # now_time = time.time()
            while True:
                screenshot = np.uint8(sct.grab(monitor))  # 捕获屏幕截图
                screenshot = cv2.resize(screenshot, (new_width, new_height)) # 修改大小
                screenshot = cv2.cvtColor(screenshot, cv2.COLOR_RGB2BGR)  # 将截图转换为OpenCV格式(BGR)
                # print(time.time() - now_time)
                # now_time = time.time()
                predictions, visualized_output = visualization.run_model_image(screenshot)
                cv2.namedWindow("Screen", cv2.WINDOW_NORMAL)  # 创建一个窗口,可手动调整大小
                cv2.imshow("Screen", cv2.resize(visualized_output.get_image()[:, :, ::-1], (width, height)))
                cv2.waitKey(100)
            # from UI.screen_recognition_window import Screen
            # screen = Screen(monitor,scale_factor)
            # now_time = time.time()
            # for vis in visualization.run_model_video(screen,screen=True):
            #     print(time.time() - now_time)
            #     now_time = time.time()
            #     cv2.namedWindow("Screen", cv2.WINDOW_NORMAL)  # 创建一个窗口,可手动调整大小
            #     cv2.imshow("Screen", cv2.resize(cv2.cvtColor(vis,cv2.COLOR_BGR2RGB), (width, height)))
            #     cv2.waitKey(100)

# 销毁所有OpenCV窗口
cv2.destroyAllWindows()

main_window.py

import os
import numpy as np
from tkinter import ttk
from mss import mss
import tqdm  # 显示进度条
import pickle  # 保存变量用
import tkinter as tk  # gui用
import tkinter.messagebox  # 弹出来的对话框
import tkinter.filedialog  # 文件相关窗口
import cv2
from PIL import Image, ImageTk
from set_up_window import SetUpWindow
from more_operations_window import MoreOperationsWindow
from screen_recognition_window import ScreenRecognitionWindow, SelectScreenArea, Screen
from detectron2.data.detection_utils import read_image
from start_up.Setup_cfg import setup_cfg
from start_up.Load_My_model import Visualization
from threading import Thread  # 进程


class MainWindow(tk.Tk):
    # 主窗口内有三种功能:图片,视频,实时(屏幕和网络摄像头)
    def __init__(self):
        super().__init__()
        self.path = "data/path_data.txt"
        self.default_path = []  # 默认模型位置,型配置位置和文件保存位置
        # 子窗口有设置、更多操作、视频播放(改为调用软件播放,已经废除)、屏幕处理,每种同时只能存在一个
        self.now_window = {"Set_Up_Window": None, "More_Operations_Window": None,
                           "Screen_Recognition_Window": None}  # 当前存在的子窗口
        self.title("i道i的实例分割系统")  # 给主窗口起一个名字
        self.geometry("1280x720+200+100")  # 大小
        self.config(menu=self.Generate_Menu())  # 生成菜单栏,窗口与菜单关联

        self.now_num = -1  # 当前选择的功能
        self.l_frame = tk.LabelFrame(self, text="功能", bg='gainsboro')  # 左边功能栏
        # 功能栏的按钮
        self.l_frame.button_all = [
            tk.Button(self.l_frame, text="图\n片", font=("NSimSun", 20, "bold"), command=lambda i=0: self.Goto_Ui(i)),
            tk.Button(self.l_frame, text="视\n频", font=("NSimSun", 20, "bold"), command=lambda i=1: self.Goto_Ui(i)),
            tk.Button(self.l_frame, text="实\n时", font=("NSimSun", 20, "bold"), command=lambda i=2: self.Goto_Ui(i))]
        self.l_frame.columnconfigure(0, weight=1)
        for i in range(len(self.l_frame.button_all)):
            self.l_frame.button_all[i].grid(row=i, column=0, sticky="nsew", padx=10, pady=4)
            self.l_frame.rowconfigure(i, weight=1)
        self.l_frame.grid(row=0, column=0, sticky="nsew")

        self.r_frame = tk.LabelFrame(self, text="数据", )  # 右边的数据栏框架
        self.data_ui = [tk.Frame(self.r_frame), tk.Frame(self.r_frame), tk.Frame(self.r_frame)]  # 三个数据集UI
        for i in range(3):
            self.data_ui[i].rowconfigure(0, weight=1)
            self.data_ui[i].rowconfigure(1, weight=25)
            self.data_ui[i].columnconfigure(0, weight=1)
        self.data_ui[2].rowconfigure(1, weight=1)
        # 第一个,图片处理========================================================================================
        # 三个功能各不相同,第一个为图片处理,三个部分(顶部的步骤栏,下面的图片栏和操作栏)
        # 先是步骤栏
        self.up_frame_image = tk.LabelFrame(self.data_ui[0], text="步骤")
        self.up_frame_image.rowconfigure(0, weight=1)
        self.up_frame_image.columnconfigure(0, weight=1)
        self.up_frame_image.columnconfigure(1, weight=1)
        self.up_frame_image.columnconfigure(2, weight=1)
        # 图片 功能:选择,处理,保存
        self.now_image_step = -1  # 当前处于哪个功能框架中
        self.now_image = [ImageTk.PhotoImage(Image.new('RGB', (0, 0))), None, None]  # 当前图片,第一个是输入的原图,第二个是能直接扔进模型的图片
        self.now_image_output = [None, None]  # 当前图片处理结果
        self.up_frame_image.buttons = [
            tk.Button(self.up_frame_image, text="选择图片", command=lambda i=0: self.Goto_Image_Step(i)),  # 第一步,选择图片
            tk.Button(self.up_frame_image, text="开始处理", command=lambda i=1: self.Goto_Image_Step(i)),  # 第二部,处理
            tk.Button(self.up_frame_image, text="保存结果", command=lambda i=2: self.Goto_Image_Step(i)),  # 第三步,保存
        ]
        self.up_frame_image.buttons[1]['state'] = 'disabled'  # 先禁用后两个,选了图片才能进去
        self.up_frame_image.buttons[2]['state'] = 'disabled'

        for i in range(len(self.up_frame_image.buttons)):
            self.up_frame_image.buttons[i]['background'] = "black"  # 背景颜色
            self.up_frame_image.buttons[i]['foreground'] = "white"  # 文字颜色
            self.up_frame_image.buttons[i].config(font=("NSimSun", 14, "bold"))  # 修改文字样式
            self.up_frame_image.buttons[i].grid(row=0, column=i, sticky="wesn", padx=4, pady=4)
        self.up_frame_image.buttons[0]['background'] = "orange"
        self.up_frame_image.grid(row=0, column=0, sticky="wesn")
        # 下面的图片栏和操作栏
        self.down_frame_image = tk.Frame(self.data_ui[0])
        # 图片栏
        self.down_frame_image.l_frame = tk.LabelFrame(self.down_frame_image, text="图片")
        self.down_frame_image.scrollbar_x = tk.Scrollbar(self.down_frame_image.l_frame, orient=tk.HORIZONTAL)  # 滚动条x
        self.down_frame_image.scrollbar_y = tk.Scrollbar(self.down_frame_image.l_frame, orient=tk.VERTICAL)  # 滚动条y
        self.down_frame_image.image_canvas = tk.Canvas(self.down_frame_image.l_frame,
                                                       xscrollcommand=self.down_frame_image.scrollbar_x.set,
                                                       yscrollcommand=self.down_frame_image.scrollbar_y.set, )
        self.down_frame_image.image_canvas.image_id = \
            self.down_frame_image.image_canvas.create_image(0, 0, anchor="nw",
                                                            image=ImageTk.PhotoImage(Image.new('RGB', (0, 0))))  # 先不放图片
        self.down_frame_image.image_canvas.configure(scrollregion=(0, 0, 0, 0))  # 更新Canvas的滚动区域
        self.down_frame_image.image_canvas.update()  # 更新canvas以显示新图片
        self.down_frame_image.scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)  # 靠下,拉满x
        self.down_frame_image.scrollbar_x.config(command=self.down_frame_image.image_canvas.xview)
        self.down_frame_image.scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)  # 靠右,拉满y
        self.down_frame_image.scrollbar_y.config(command=self.down_frame_image.image_canvas.yview)
        self.down_frame_image.image_canvas.pack(fill=tk.BOTH, expand=True)  # 中间,且不扩充父框架大小
        self.down_frame_image.l_frame.grid(row=0, column=0, sticky="wesn")
        # 操作栏
        self.down_frame_image.r_frame = tk.LabelFrame(self.down_frame_image, text="操作", bg='gainsboro')
        # 第一步,选择图片
        self.down_frame_image.button_select_image = tk.Button(self.down_frame_image.r_frame, text="选\n择\n图\n片\n",
                                                              font=("Microsoft YaHei", 18,),
                                                              command=self.Select_Image)  # 选择图片按钮
        # 第二步,进行处理
        self.down_frame_image.button_start_segmentation = tk.Button(self.down_frame_image.r_frame, text="开始\n分割",
                                                                    font=("Microsoft YaHei", 10,),
                                                                    command=self.Start_Segmentation_Image)
        # 分割结束后,如果要更多操作,转到更多操作弹窗处理
        self.down_frame_image.button_more_operations = tk.Button(self.down_frame_image.r_frame, text="更多\n操作",
                                                                 font=("Microsoft YaHei", 10,),
                                                                 command=self.More_Operations)  # 分割结束后
        self.down_frame_image.button_more_operations['state'] = 'disabled'  # 更多操作开始是禁用状态
        # 第三步,输出
        self.down_frame_image.button_save = tk.Button(self.down_frame_image.r_frame, text="保存\n结果",
                                                      font=("Microsoft YaHei", 10,),
                                                      command=self.Save_Image)  # 打开一个文件保存对话框
        self.down_frame_image.button_open_save_path = tk.Button(self.down_frame_image.r_frame, text="打开\n默认\n保存\n位置",
                                                                font=("Microsoft YaHei", 10,),
                                                                command=self.Open_Default_Save_Path)  # 打开默认保存位置
        self.down_frame_image.r_frame.rowconfigure(0, weight=1)
        self.down_frame_image.r_frame.rowconfigure(1, weight=1)
        self.down_frame_image.r_frame.columnconfigure(0, weight=1)
        self.down_frame_image.r_frame.columnconfigure(1, weight=1)
        self.down_frame_image.r_frame.grid(row=0, column=1, sticky="wesn")

        self.down_frame_image.rowconfigure(0, weight=1)
        self.down_frame_image.columnconfigure(0, weight=5)
        self.down_frame_image.columnconfigure(1, weight=1)
        self.down_frame_image.grid(row=1, column=0, sticky="wesn")

        # 第二个,视频处理========================================================================================
        # 和图片处理极其相似,去除更多操作按钮。增加取消分割功能(因为时间太长)
        # 先是步骤栏
        self.up_frame_video = tk.LabelFrame(self.data_ui[1], text="步骤")
        self.up_frame_video.rowconfigure(0, weight=1)
        self.up_frame_video.columnconfigure(0, weight=1)
        self.up_frame_video.columnconfigure(1, weight=1)
        self.up_frame_video.columnconfigure(2, weight=1)
        # 视频 功能:选择,处理,保存
        self.now_video_step = -1  # 当前处于哪个功能框架中
        self.now_video = [None, None]
        self.now_video_output = [None, None]  # 视频处理结果
        self.up_frame_video.buttons = [
            tk.Button(self.up_frame_video, text="选择视频", command=lambda i=0: self.Goto_Video_Step(i)),  # 第一步,选择图片
            tk.Button(self.up_frame_video, text="开始处理", command=lambda i=1: self.Goto_Video_Step(i)),  # 第二部,处理
            tk.Button(self.up_frame_video, text="保存结果", command=lambda i=2: self.Goto_Video_Step(i)),  # 第三步,保存
        ]
        self.up_frame_video.buttons[1]['state'] = 'disabled'  # 先禁用后两个,选了视频才能进去
        self.up_frame_video.buttons[2]['state'] = 'disabled'
        for i in range(len(self.up_frame_video.buttons)):
            self.up_frame_video.buttons[i]['background'] = "black"  # 背景颜色
            self.up_frame_video.buttons[i]['foreground'] = "white"  # 文字颜色
            self.up_frame_video.buttons[i].config(font=("NSimSun", 14, "bold"))  # 修改文字样式
            self.up_frame_video.buttons[i].grid(row=0, column=i, sticky="wesn", padx=4, pady=4)
        self.up_frame_video.buttons[0]['background'] = "orange"
        self.up_frame_video.grid(row=0, column=0, sticky="wesn")
        # 下面的视频栏和操作栏
        self.down_frame_video = tk.Frame(self.data_ui[1])
        # 视频栏
        self.down_frame_video.l_frame = tk.LabelFrame(self.down_frame_video, text="视频")
        # 做一个Canvas,绑定左键点击事件,左键点击后播放视频
        self.down_frame_video.video_canvas = tk.Canvas(self.down_frame_video.l_frame, )
        self.down_frame_video.video_canvas.image_id = \
            self.down_frame_video.video_canvas.create_image(0, 0, anchor="nw",
                                                            image=ImageTk.PhotoImage(Image.new('RGB', (0, 0))))  # 先不放图片
        self.down_frame_video.video_canvas.pack(fill=tk.BOTH, expand=True)
        self.down_frame_video.l_frame.grid(row=0, column=0, sticky="wesn")
        self.down_frame_video.video_canvas.bind("<Button-1>", self.Play_Video)
        # 操作栏
        self.down_frame_video.r_frame = tk.LabelFrame(self.down_frame_video, text="操作", bg='gainsboro')
        # 第一步,选择视频
        self.down_frame_video.button_select_video = tk.Button(self.down_frame_video.r_frame, text="选\n择\n视\n频",
                                                              font=("Microsoft YaHei", 18,),
                                                              command=self.Select_Video)  # 选择图片按钮
        # 第二步,进行处理
        self.down_frame_video.button_start_segmentation = tk.Button(self.down_frame_video.r_frame, text="开始\n分割",
                                                                    font=("Microsoft YaHei", 10,),
                                                                    command=self.Start_Segmentation_Video)
        # 打开默认保存位置
        self.down_frame_video.button_open_save_path = tk.Button(self.down_frame_video.r_frame, text="打开\n默认\n保存\n位置",
                                                                font=("Microsoft YaHei", 10,),
                                                                command=self.Open_Default_Save_Path)  # 打开默认保存位置
        self.down_frame_video.r_frame.rowconfigure(0, weight=1)
        self.down_frame_video.r_frame.rowconfigure(1, weight=1)
        self.down_frame_video.r_frame.columnconfigure(0, weight=1)
        self.down_frame_video.r_frame.columnconfigure(1, weight=1)
        self.down_frame_video.r_frame.grid(row=0, column=1, sticky="wesn")

        self.down_frame_video.rowconfigure(0, weight=1)
        self.down_frame_video.columnconfigure(0, weight=5)
        self.down_frame_video.columnconfigure(1, weight=1)
        self.down_frame_video.grid(row=1, column=0, sticky="wesn")

        # 第三个功能,屏幕处理========================================================================================
        # 选择屏幕区域,开始处理。或者链接ip摄像头(因为电脑不带摄像头)
        # 屏幕处理,选择屏幕区域按钮和文本框,开始处理按钮,缩放系数滑条,两种方式单选框
        # 选择屏幕区域
        def validate_number(P):  # 判断是否为数字
            if P.isdigit() or (P == '' and P is not None):
                return True
            return False

        self.vcmd = (self.register(validate_number), '%P')  # 验证输入是否为数字
        self.up_frame_screen = tk.LabelFrame(self.data_ui[2], text="屏幕")
        # 屏幕位置关联变量,top,left,width,height
        self.up_frame_screen.position = [tk.IntVar(), tk.IntVar(), tk.IntVar(), tk.IntVar()]
        self.up_frame_screen.position_label = [tk.Label(self.up_frame_screen, text="top:"),
                                               tk.Label(self.up_frame_screen, text="left:"),
                                               tk.Label(self.up_frame_screen, text="width:"),
                                               tk.Label(self.up_frame_screen, text="height:"), ]
        self.up_frame_screen.position_entry = []  # 截屏范围文本框
        for i in range(2):
            for j in range(2):
                self.up_frame_screen.position_entry.append(
                    tk.Entry(self.up_frame_screen, exportselection=0, validate="key",
                             validatecommand=self.vcmd,  # 表示每次按键时都会调用验证函数
                             textvariable=self.up_frame_screen.position[2 * i + j]))
                self.up_frame_screen.position_label[2 * i + j].grid(row=i, column=2 * j)
                self.up_frame_screen.position_entry[2 * i + j].grid(row=i, column=2 * j + 1, sticky="we")

        def Open_SelectScreenArea():  # 打开选择屏幕区域窗口
            SelectScreenArea(self)

        self.up_frame_screen.select_screen_range = tk.Button(self.up_frame_screen, text="选择屏\n幕范围",
                                                             command=Open_SelectScreenArea, )
        self.up_frame_screen.select_screen_range.grid(row=0, column=4, )
        # cv2 模式
        # 创建一个 IntVar 对象来存储开关的状态
        self.up_frame_screen.switch_var = tk.IntVar()
        self.up_frame_screen.switch_var.set(0)

        self.up_frame_screen.cv2_checkbutton = tk.Checkbutton(self.up_frame_screen, text="cv2窗口模式",
                                                              variable=self.up_frame_screen.switch_var)
        self.up_frame_screen.cv2_checkbutton.grid(row=1, column=4, )

        # 单选处理模式
        self.up_frame_screen.processing_mode = tk.IntVar()  # 单选按钮的值,用.get()可以取出来,初始0
        self.up_frame_screen.processing_mode_radiobutton = [
            tk.Radiobutton(self.up_frame_screen, text="图片式处理", variable=self.up_frame_screen.processing_mode,
                           value=0),
            tk.Radiobutton(self.up_frame_screen, text="流式处理", variable=self.up_frame_screen.processing_mode,
                           value=1)]
        self.up_frame_screen.processing_mode_radiobutton[0].grid(row=2, column=0, )
        self.up_frame_screen.processing_mode_radiobutton[1].grid(row=2, column=1, )

        # 缩放系数滑条
        def Update_Scale_Value(event):
            # 获取滑条当前的值
            current_value = self.up_frame_screen.scale_factor_scale.get()
            self.up_frame_screen.scale_factor = current_value

        self.up_frame_screen.scale_factor = 0.5
        self.up_frame_screen.scale_factor_label = tk.Label(self.up_frame_screen, text="缩放系数:")
        self.up_frame_screen.scale_factor_scale = tk.Scale(self.up_frame_screen, from_=0.1, to=1, resolution=0.1,
                                                           orient='horizontal', command=Update_Scale_Value)
        self.up_frame_screen.scale_factor_scale.set(self.up_frame_screen.scale_factor)  # 设置初始值
        self.up_frame_screen.scale_factor_label.grid(row=2, column=2, )
        self.up_frame_screen.scale_factor_scale.grid(row=2, column=3, sticky="we")
        # 开始处理

        self.up_frame_screen.start_processing = tk.Button(self.up_frame_screen, text="开始处理", command=self.Play_Screen)
        self.up_frame_screen.start_processing.grid(row=2, column=4, )

        self.up_frame_screen.rowconfigure(0, weight=1)
        self.up_frame_screen.rowconfigure(1, weight=1)
        self.up_frame_screen.rowconfigure(2, weight=1)
        for i in range(5):
            self.up_frame_screen.columnconfigure(i, weight=1)
        self.up_frame_screen.grid(row=0, column=0, sticky="wesn")
        # ip摄像头,ip文本框,开始处理按钮
        self.down_frame_screen = tk.LabelFrame(self.data_ui[2], text="网络摄像头")

        self.down_frame_screen.rowconfigure(0, weight=1)
        self.down_frame_screen.columnconfigure(0, weight=1)
        self.down_frame_screen.grid(row=1, column=0, sticky="wesn")
        self.down_frame_screen.ip_l = [tk.Label(self.down_frame_screen, text="ip地址:"),
                                       tk.Label(self.down_frame_screen, text="用户名:"),
                                       tk.Label(self.down_frame_screen, text="密码:")]
        self.down_frame_screen.ip_e = [tk.Entry(self.down_frame_screen, exportselection=0),
                                       tk.Entry(self.down_frame_screen, exportselection=0),
                                       tk.Entry(self.down_frame_screen, exportselection=0, show="*"), ]
        self.down_frame_screen.ip_e[0].insert(0, "192.168.31.133:8081")
        self.down_frame_screen.ip_e[1].insert(0, "admin")
        self.down_frame_screen.ip_e[2].insert(0, "admin")
        self.down_frame_screen.ip_l[0].grid(row=1, column=0)
        self.down_frame_screen.ip_e[0].grid(row=1, column=1, sticky="we", columnspan=2, )
        self.down_frame_screen.ip_l[1].grid(row=0, column=0)
        self.down_frame_screen.ip_e[1].grid(row=0, column=1, sticky="we")
        self.down_frame_screen.ip_l[2].grid(row=0, column=2)
        self.down_frame_screen.ip_e[2].grid(row=0, column=3, sticky="we")
        # 开始摄像头处理
        self.down_frame_screen.start_processing = tk.Button(self.down_frame_screen, text="开始处理",
                                                            command=lambda ip=True: self.Play_Screen(ip))
        self.down_frame_screen.start_processing.grid(row=1, column=3, )
        tk.Label(self.down_frame_screen, text="处理模式,窗口模式,缩放系数延用屏幕处理部分,如果摄像头未打开,会受到阻塞,是正常现象").grid(row=2, column=0,
                                                                                                  columnspan=4, )
        self.down_frame_screen.rowconfigure(0, weight=1)
        self.down_frame_screen.rowconfigure(1, weight=1)
        self.down_frame_screen.rowconfigure(2, weight=1)
        for i in range(5):
            self.down_frame_screen.columnconfigure(i, weight=1)
        # 结束
        self.r_frame.rowconfigure(0, weight=1)
        self.r_frame.columnconfigure(0, weight=1)
        self.r_frame.grid(row=0, column=1, sticky="nsew")

        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=10)
        self.rowconfigure(0, weight=1)
        self.Initialize_Model()  # 初始化模型

        # 设置按钮的回调

    def Set_Up_Callback(self):
        # 没设置窗口或者窗口已经不存在
        if self.now_window["Set_Up_Window"] is None or not self.now_window["Set_Up_Window"].winfo_exists():
            set_up_window = SetUpWindow(main_window)  # 生成设置窗口
            self.now_window["Set_Up_Window"] = set_up_window  # 变量添加到存在的子窗口中
        else:  # 有窗口了
            self.now_window["Set_Up_Window"].deiconify()  # 取消最小化
            self.now_window["Set_Up_Window"].lift()  # 置于所有窗口顶端

    # 创建菜单栏,暂时只有一个设置按钮
    def Generate_Menu(self):
        menubar = tk.Menu(self)  # 菜单栏
        menubar.add_command(label='设置', command=self.Set_Up_Callback)  # 菜单添加设置项(设置使用的模型默认文件保存位置)
        return menubar

    # 读取文件中的默认位置,并且加载模型。
    def Initialize_Model(self, repeat=False):
        # 保存的文件我都设在同一目录下吧
        try:
            file = open(self.path, 'rb')  # 读取配置文件
            data = pickle.load(file)
            self.default_path = data
            file.close()  # 关闭文件
        except FileNotFoundError:  # 文件未找到
            # 使用默认路径创建一个新文件
            if repeat:  # 重建路径文件后还不行
                tk.messagebox.showwarning(title='警告!', message='路径文件错误,无法重建路径文件')
                exit(-1)
            else:
                self.Change_Path_Data(["data\model_final.pth", "data\My_BlendMask.yaml", "out_put"])
                self.Initialize_Model(True)  # 重新初始化
                return 0
        # 读取完配置文件了,加载一下模型
        try:
            self.cfg = setup_cfg(self.default_path[0], self.default_path[1])  # 导入配置文件和模型文件
            self.visualization = Visualization(self.cfg)  # 初始化可视化器
        except:
            tk.messagebox.showwarning(title='警告!', message='无法导入配置文件和模型文件')

    def Change_Path_Data(self, list):  # 修改路径文件,将list列表保存到path_data.txt中
        try:
            file = open(self.path, 'wb')  # 东西少,直接覆盖
            pickle.dump(list, file)
            file.close()
        except:
            print("错误")
            exit(-1)

    def Resetting_Data_Bar(self):  # 重置数据栏,遍历所有控件,取消显示
        for i in self.r_frame.grid_slaves():
            i.grid_forget()

    def Goto_Ui(self, num):  # 选择功能
        if self.now_num == num:  # 当前播放窗口一致,直接不管
            return
        else:
            self.now_num = num
            self.Resetting_Data_Bar()  # 取消显示数据栏中的控件
            self.data_ui[num].grid(row=0, column=0, sticky="wesn")  # 将需求的控件显示出来

    def Goto_Image_Step(self, num):  # 选择图片处理步骤
        if num != self.now_image_step:  # 和当前窗口不一致才继续
            for i in self.down_frame_image.r_frame.grid_slaves():  # 遍历所有控件,取消显示
                i.grid_forget()
            if num == 0:
                # 显示选择图片按钮
                self.down_frame_image.button_select_image.grid(row=0, column=0, columnspan=2, rowspan=2, sticky="wesn",
                                                               padx=10, pady=4)
                # 取消显示图片
                self.down_frame_image.image_canvas.itemconfig(self.down_frame_image.image_canvas.image_id,
                                                              image=ImageTk.PhotoImage(Image.new('RGB', (0, 0))))
                self.down_frame_image.image_canvas.configure(scrollregion=(0, 0, 0, 0))  # 更新Canvas的滚动区域
                self.down_frame_image.image_canvas.update()  # 更新canvas以显示新图片
                self.up_frame_image.buttons[1]['state'] = 'disabled'  # 禁用后两个,选了图片才能进去
                self.up_frame_image.buttons[2]['state'] = 'disabled'
                self.up_frame_image.buttons[1]['background'] = "black"
                self.up_frame_image.buttons[2]['background'] = "black"
                self.up_frame_image.buttons[0]['background'] = "green"
                self.down_frame_image.button_start_segmentation['state'] = 'normal'  # 启用开始分割按钮
                self.down_frame_image.button_more_operations['state'] = 'disabled'  # 禁用更多操作按钮
            elif num == 1:
                self.down_frame_image.button_start_segmentation.grid(row=0, column=0, columnspan=2, sticky="wesn",
                                                                     padx=10, pady=4)
                self.down_frame_image.button_more_operations.grid(row=1, column=0, columnspan=2, sticky="wesn", padx=10,
                                                                  pady=4)
            elif num == 2:
                self.down_frame_image.button_save.grid(row=0, column=0, columnspan=2, sticky="wesn", padx=10, pady=4)
                self.down_frame_image.button_open_save_path.grid(row=1, column=0, columnspan=2, sticky="wesn", padx=10,
                                                                 pady=4)
            self.now_image_step = num

    def Select_Image(self):  # 选择图片按钮的回调
        file_path = tk.filedialog.askopenfilename(filetypes=[("图片文件", '*.jpeg;*.jpg;*.png')],)  # 文件选择对话框
        if file_path.strip() != '':  # 是空的时候,往往没有选择,直接关闭窗口
            try:
                self.now_image[0] = Image.open(file_path)  # 窗口中显示的图片
                self.now_image[1] = read_image(file_path, format="BGR")  # 要送进模型的图片
                self.down_frame_image.image_canvas.image = ImageTk.PhotoImage(self.now_image[0])
                self.down_frame_image.image_canvas.itemconfig(self.down_frame_image.image_canvas.image_id,
                                                              image=self.down_frame_image.image_canvas.image)
                self.down_frame_image.image_canvas.configure(
                    scrollregion=(0, 0, self.down_frame_image.image_canvas.image.width(),
                                  self.down_frame_image.image_canvas.image.height()))  # 更新Canvas的滚动区域
                self.down_frame_image.image_canvas.update()  # 更新canvas以显示新图片
            except:
                tk.messagebox.showwarning(title='警告!', message='图片文件错误,无法读取图片文件')
                self.up_frame_image.buttons[1]['state'] = 'disabled'  # 禁用后两个,防止没有图片
                self.up_frame_image.buttons[2]['state'] = 'disabled'
                self.up_frame_image.buttons[1]['background'] = "black"
                self.up_frame_image.buttons[2]['background'] = "black"
                return
            # 走到这表明没问题了,解除禁用
            self.up_frame_image.buttons[1]['state'] = 'normal'
            self.up_frame_image.buttons[1]['background'] = "green"  # 进行开始处理步骤
            self.up_frame_image.buttons[0]['background'] = "royalblue"  # 完成图片选择
            # 转到开始处理
            self.Goto_Image_Step(1)

    def Start_Segmentation_Image(self):  # 开始分割图片
        def Segmentation_Image():
            try:
                # 对图片进行处理
                self.now_image_output[0], self.now_image_output[1] = self.visualization.run_model_image(
                    self.now_image[1])
            except:
                tk.messagebox.showwarning(title='警告!', message='模型错误,无法使用模型处理图片')
                return
            # 得到处理结果了
            self.now_image[1] = Image.fromarray(self.now_image_output[1].get_image()[:, :, ::-1])
            self.now_image[2] = ImageTk.PhotoImage(self.now_image[1])
            self.down_frame_image.image_canvas.itemconfig(self.down_frame_image.image_canvas.image_id,
                                                          image=self.now_image[2])  # 替换图片为处理好的结果
            self.down_frame_image.image_canvas.configure(
                scrollregion=(0, 0, self.now_image[2].width(), self.now_image[2].height()))  # 更新Canvas的滚动区域
            self.down_frame_image.image_canvas.update()  # 更新canvas以显示新图片
            self.down_frame_image.button_start_segmentation['state'] = 'disabled'  # 禁用开始分割按钮,
            self.down_frame_image.button_more_operations['state'] = 'normal'  # 恢复更多操作按钮和保存结果按钮
            self.up_frame_image.buttons[2]['state'] = 'normal'
            self.up_frame_image.buttons[2]['background'] = "green"
            self.up_frame_image.buttons[1]['background'] = "royalblue"
            self.waiting_dialog.destroy()  # 执行完毕,销毁等待对话框

        try:
            self.Waiting_Dialog()
            thread = Thread(target=Segmentation_Image)
            thread.start()  # 启用新线程分割,避免阻塞窗口
        except:
            tk.messagebox.showwarning(title='警告!', message='图片分割失败,无法处理')

    def More_Operations(self):  # 更多操作按钮回调
        # 没更多操作窗口或者窗口已经不存在
        if self.now_window["More_Operations_Window"] is None or not self.now_window[
            "More_Operations_Window"].winfo_exists():
            more_operations_window = MoreOperationsWindow(main_window)  # 生成更多操作
            self.now_window["More_Operations_Window"] = more_operations_window  # 变量添加到存在的子窗口中
        else:  # 有窗口了
            self.now_window["More_Operations_Window"].deiconify()  # 取消最小化
            self.now_window["More_Operations_Window"].lift()  # 置于所有窗口顶端

    def Save_Image(self, img=None, variable=None):  # 保存图片
        if img is None and variable is None:
            img = self.now_image[1]
            variable = self.now_image_output[0]
        file_img = tk.filedialog.asksaveasfilename(filetypes=[("图片文件", '*.jpeg;*.jpg;*.png')], defaultextension=".jpeg",
                                                    initialfile="分割图片",initialdir =self.default_path[2] )
        if file_img.strip() != '':  # 是空的时候,往往没有选择,直接关闭窗口
            img.save(file_img)  # 保存图片
        if variable is not None:  # 给出了模型输出
            file_v = tk.filedialog.asksaveasfilename(filetypes=[("模型输出", '*.bin'), ], defaultextension=".bin",
                                                      initialfile="模型输出",initialdir =self.default_path[2])
            if file_v.strip() != '':  # 是空的时候,往往没有选择,直接关闭窗口
                file = open(file_v, 'wb')
                pickle.dump(variable, file)
                file.close()

    def Open_Default_Save_Path(self):  # 打开默认保存位置
        os.startfile(self.default_path[2])

    # 缩放图片大小,proportion为调整到的大小比例,size为调整到的大小,size优先级更高
    def Zoom_Image(self, img, proportion=None, size=None):
        width, height = img.size
        if size is None:
            size = [0, 0]
        if not proportion is None:
            size[0] = int(width * proportion)
            size[1] = int(height * proportion)
        resized_image = img.resize(size)
        return resized_image

    def Goto_Video_Step(self, num):  # 选择视频处理的步骤
        if num != self.now_video_step:  # 和当前窗口不一致才继续
            for i in self.down_frame_video.r_frame.grid_slaves():  # 遍历所有控件,取消显示
                i.grid_forget()
            if num == 0:
                # 显示选择图片按钮
                self.down_frame_video.button_select_video.grid(row=0, column=0, columnspan=2, rowspan=2, sticky="wesn",
                                                               padx=10, pady=4)
                # 取消显示图片
                self.down_frame_video.video_canvas.itemconfig(self.down_frame_video.video_canvas.image_id,
                                                              image=ImageTk.PhotoImage(Image.new('RGB', (0, 0))))
                self.now_video[1] = None  # 删除地址字符串
                self.down_frame_video.video_canvas['cursor'] = 'arrow'  # 鼠标样式更改
                self.down_frame_video.video_canvas.update()  # 更新canvas以显示新图片
                self.up_frame_video.buttons[1]['state'] = 'disabled'  # 禁用后两个,选了图片才能进去
                self.up_frame_video.buttons[2]['state'] = 'disabled'
                self.up_frame_video.buttons[1]['background'] = "black"
                self.up_frame_video.buttons[2]['background'] = "black"
                self.up_frame_video.buttons[0]['background'] = "green"

            elif num == 1:
                # 放入开始分割按钮
                self.down_frame_video.button_start_segmentation.grid(row=0, column=0, columnspan=2, rowspan=2,
                                                                     sticky="wesn", padx=10, pady=4)
            elif num == 2:
                self.down_frame_video.button_open_save_path.grid(row=0, column=0, columnspan=2, rowspan=2,
                                                                 sticky="wesn", padx=10, pady=4)
            self.now_video_step = num

    def Select_Video(self):  # 选择视频
        file_path = tk.filedialog.askopenfilename(filetypes=[("视频文件", '*.mp4;*.mkv')],)  # 文件选择对话框
        if file_path.strip() != '':  # 是空的时候,往往没有选择,直接关闭窗口
            try:
                self.now_video[1] = file_path  # 读取的视频文件位置
                video = cv2.VideoCapture(file_path)
                ret, frame = video.read()  # 读取一帧
                video.release()  # 释放VideoCapture对象
                if ret:  # 成功读取
                    # OpenCV图像是BGR格式的,转换为RGB并转为Tkinter可以使用的格式,顺便修改一下大小
                    self.now_video[0] = ImageTk.PhotoImage(self.Zoom_Image(Image.fromarray(
                        cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)), size=[self.down_frame_video.l_frame.winfo_width(),
                                                                       self.down_frame_video.l_frame.winfo_height()]))
                    self.down_frame_video.video_canvas.itemconfig(self.down_frame_video.video_canvas.image_id,
                                                                  image=self.now_video[0])
                    self.down_frame_video.video_canvas.update()  # 更新canvas以显示新图片
                    self.down_frame_video.video_canvas['cursor'] = 'hand2'
                    self.down_frame_video.button_start_segmentation['state'] = 'normal'  # 启用开始分割按钮
            except:
                tk.messagebox.showwarning(title='警告!', message='视频文件错误,无法读取视频文件')
                self.up_frame_video.buttons[1]['state'] = 'disabled'  # 禁用后两个,防止没有图片
                self.up_frame_video.buttons[2]['state'] = 'disabled'
                self.up_frame_video.buttons[1]['background'] = "black"
                self.up_frame_video.buttons[2]['background'] = "black"
                self.now_video[1] = None  # 删除地址字符串
                self.down_frame_video.video_canvas['cursor'] = 'arrow'  # 鼠标样式更改
                return
            # 走到这表明没问题了,解除禁用
            self.up_frame_video.buttons[1]['state'] = 'normal'
            self.up_frame_video.buttons[1]['background'] = "green"  # 进行开始处理步骤
            self.up_frame_video.buttons[0]['background'] = "royalblue"  # 完成图片选择
            # 转到开始处理
            self.Goto_Video_Step(1)

    def Start_Segmentation_Video(self):  # 开始分割视频
        def Segmentation_Video():
            try:
                # 对视频进行处理
                video = cv2.VideoCapture(self.now_video[1])
                width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
                height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
                frames_per_second = video.get(cv2.CAP_PROP_FPS)  # 帧率
                num_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))  # 总帧数
                output_fname = \
                    os.path.splitext(os.path.join(self.default_path[2], os.path.basename(self.now_video[1])))[
                        0] + "_out.mkv"  # 文件输出位置,输出到默认输出位置,名字和输入文件名字相同,后缀改为mkv
                # 不避免覆盖
                output_file = cv2.VideoWriter(  # 创建视频写入器
                    filename=output_fname,
                    fourcc=cv2.VideoWriter_fourcc(*"MPEG"),  # 或者MPEG
                    fps=float(frames_per_second),
                    frameSize=(width, height),
                    isColor=True,
                )
                num = 0
                for vis in tqdm.tqdm(self.visualization.run_model_video(video)):
                    output_file.write(vis)
                    num += 1
                    self.waiting_dialog.progress_var.set(int(num / num_frames * 100))  # 进度条
                    if not self.waiting_dialog.winfo_exists():  # 对话框关闭后终止运行
                        break
                video.release()  # 解除占用
                output_file.release()

            except:
                tk.messagebox.showwarning(title='警告!', message='模型错误,无法使用模型处理图片')
                return
            # 得到处理结果了
            # 显示图片
            video = cv2.VideoCapture(output_fname)
            ret, frame = video.read()  # 读取一帧
            video.release()  # 释放VideoCapture对象
            if ret:  # 成功读取
                self.now_video[1] = output_fname  # 更改点击播放的视频
                self.now_video[0] = ImageTk.PhotoImage(self.Zoom_Image(Image.fromarray(  # 更改显示的图片
                    cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)), size=[self.down_frame_video.l_frame.winfo_width(),
                                                                   self.down_frame_video.l_frame.winfo_height()]))
                self.down_frame_video.video_canvas.itemconfig(self.down_frame_video.video_canvas.image_id,
                                                              image=self.now_video[0])
            self.down_frame_video.video_canvas.update()  # 更新canvas以显示新图片
            self.up_frame_video.buttons[2]['state'] = 'normal'
            self.up_frame_video.buttons[2]['background'] = "green"
            self.up_frame_video.buttons[1]['background'] = "royalblue"
            self.down_frame_video.button_start_segmentation['state'] = 'disabled'  # 禁用开始分割按钮
            self.waiting_dialog.destroy()  # 执行完毕,销毁等待对话框
            self.Goto_Video_Step(2)  # 去往保存位置步骤

        try:
            self.Waiting_Dialog(video=True)
            thread = Thread(target=Segmentation_Video)
            thread.start()  # 启用新线程分割,避免阻塞窗口
        except:
            tk.messagebox.showwarning(title='警告!', message='视频分割失败,无法处理')

    def Play_Video(self, event):  # 播放视频
        if not self.now_video[1] is None:  # 有视频路径
            if os.path.exists(self.now_video[1]):
                # 使用默认播放器打开视频文件
                os.startfile(self.now_video[1])

    def Play_Screen(self, ip=False):  # 屏幕处理
        # 没屏幕处理窗口或者窗口已经不存在
        if self.up_frame_screen.switch_var.get() == 1:  # 进行cv2模式
            self.Handle(ip)
        else:
            if self.now_window["Screen_Recognition_Window"] is None or not self.now_window[
                "Screen_Recognition_Window"].winfo_exists():
                screen_recognition_window = ScreenRecognitionWindow(main_window, ip)  # 生成屏幕处理窗口
                self.now_window["Screen_Recognition_Window"] = screen_recognition_window  # 变量添加到存在的子窗口中
            else:  # 有窗口了
                self.now_window["Screen_Recognition_Window"].deiconify()  # 取消最小化
                self.now_window["Screen_Recognition_Window"].lift()  # 置于所有窗口顶端

    def Waiting_Dialog(self, video=False):  # 一个等待框
        self.waiting_dialog = tk.Toplevel(self)  # 一个等待对话框
        self.waiting_dialog.grab_set()  # 设置模态,阻止用户与其他窗口交互
        self.waiting_dialog.transient(self)  # 设置为临时窗口,随主窗口关闭而关闭
        self.waiting_dialog.title("正在处理")
        self.waiting_dialog.geometry("%dx%d+%d+%d" % (400, 300, self.winfo_x() + 100, self.winfo_y() + 100))
        self.waiting_dialog.resizable(False, False)  # 禁止缩放
        if video:
            waiting_label = tk.Label(self.waiting_dialog, text="请稍候,正在处理...,\n关闭此框将会结束视频分割")
            self.waiting_dialog.progress_var = tk.StringVar()
            self.waiting_dialog.progress_var.set(0)
            self.waiting_dialog.progress = ttk.Progressbar(self.waiting_dialog, orient='horizontal', length=200,
                                                           mode='determinate',
                                                           variable=self.waiting_dialog.progress_var)
            self.waiting_dialog.progress.pack(pady=50)
        else:
            waiting_label = tk.Label(self.waiting_dialog, text="请稍候,正在处理...")
        waiting_label.pack(pady=20)

    def Handle(self, ip=False):
        if ip:
            try:
                if cv2.getWindowProperty('Screen', 0) >= 0:
                    return
            except cv2.error:
                pass
            ip = f"http://{self.down_frame_screen.ip_e[1].get()}:{self.down_frame_screen.ip_e[2].get()}@{self.down_frame_screen.ip_e[0].get()}/"
            capture = cv2.VideoCapture(ip)
            # 尝试读取一帧
            ret, img = capture.read()
            if not ret:
                tk.messagebox.showwarning(title='警告!', message='无法从摄像头中读取图片')
                return
            monitor = {"top": 0
                , "left": 0
                , "width": img.shape[1]
                , "height": img.shape[0]}
            scale_factor = self.up_frame_screen.scale_factor  # 缩放系数
            screen = Screen(monitor, scale_factor, ip=capture)
            if self.up_frame_screen.processing_mode.get() == 0:
                while True:
                    try:
                        ret, img = screen.next()
                        if not ret:  # 没读取成功图片
                            break
                        _, visualized_output = self.visualization.run_model_image(img)
                        cv2.namedWindow("Screen", cv2.WINDOW_NORMAL)  # 创建一个窗口,可手动调整大小
                        cv2.imshow("Screen", cv2.resize(visualized_output.get_image()[:, :, ::-1],
                                                        (monitor["width"], monitor["height"])))
                        cv2.waitKey(100)
                        if cv2.getWindowProperty('Screen', 0) < 0:  # 窗口关闭
                            cv2.destroyAllWindows()
                            break
                    except cv2.error:
                        break

            elif self.up_frame_screen.processing_mode.get() == 1:
                for vis in self.visualization.run_model_video(screen, screen=True):
                    try:
                        cv2.namedWindow("Screen", cv2.WINDOW_NORMAL)  # 创建一个窗口,可手动调整大小
                        cv2.imshow("Screen", cv2.resize(cv2.cvtColor(vis, cv2.COLOR_BGR2RGB),
                                                        (monitor["width"], monitor["height"])))
                        cv2.waitKey(100)
                        if cv2.getWindowProperty('Screen', 0) < 0:
                            cv2.destroyAllWindows()
                            break
                    except cv2.error:
                        break
            capture.release()
        else:
            try:
                if cv2.getWindowProperty('Screen', 0) >= 0:
                    return
            except cv2.error:
                pass
            monitor = {"top": self.up_frame_screen.position[0].get()
                , "left": self.up_frame_screen.position[1].get()
                , "width": self.up_frame_screen.position[2].get()
                , "height": self.up_frame_screen.position[3].get()}
            scale_factor = self.up_frame_screen.scale_factor  # 缩放系数
            new_width = int(monitor["width"] * scale_factor)  # 缩放后的大小
            new_height = int(monitor["height"] * scale_factor)
            if self.up_frame_screen.processing_mode.get() == 0:
                sct = mss()  # 创建一个屏幕捕获对象
                while True:
                    try:
                        screenshot = np.uint8(sct.grab(monitor))  # 捕获屏幕截图
                        screenshot = cv2.resize(screenshot, (new_width, new_height))  # 修改大小
                        screenshot = cv2.cvtColor(screenshot, cv2.COLOR_RGB2BGR)  # 将截图转换为OpenCV格式(BGR)
                        _, visualized_output = self.visualization.run_model_image(screenshot)
                        cv2.namedWindow("Screen", cv2.WINDOW_NORMAL)  # 创建一个窗口,可手动调整大小
                        cv2.imshow("Screen", cv2.resize(visualized_output.get_image()[:, :, ::-1],
                                                        (monitor["width"], monitor["height"])))
                        cv2.waitKey(100)
                        if cv2.getWindowProperty('Screen', 0) < 0:
                            cv2.destroyAllWindows()
                            break
                    except cv2.error:
                        break

            elif self.up_frame_screen.processing_mode.get() == 1:
                screen = Screen(monitor, scale_factor)
                for vis in self.visualization.run_model_video(screen, screen=True):
                    try:
                        cv2.namedWindow("Screen", cv2.WINDOW_NORMAL)  # 创建一个窗口,可手动调整大小
                        cv2.imshow("Screen", cv2.resize(cv2.cvtColor(vis, cv2.COLOR_BGR2RGB),
                                                        (monitor["width"], monitor["height"])))
                        cv2.waitKey(100)
                        if cv2.getWindowProperty('Screen', 0) < 0:
                            cv2.destroyAllWindows()
                            break
                    except cv2.error:
                        break


if __name__ == "__main__":
    main_window = MainWindow()  # 创建主窗口

    main_window.mainloop()  # 开启主循环,让窗口处于显示状态

more_operations_window.py

# 更多操作窗口类,用于对分割后的图片进行更多处理
import tkinter as tk  # gui用
from tkinter import colorchooser
import numpy as np
import random
from PIL import Image, ImageTk, ImageDraw, ImageFilter


class MoreOperationsWindow(tk.Toplevel):
    def __init__(self, master):
        super().__init__(master)  # 父类调用,
        self.master = master
        self.grab_set()  # 独占焦点
        self.title("更多操作")
        # 设置大小和位置(相对于父窗口左上角)
        self.geometry("%dx%d+%d+%d" % (1024, 768, master.winfo_x() + 128, master.winfo_y() + 0))
        self.image = self.master.now_image[0]  # 原始图片
        self.now_image = self.image  # 当前处理的图片
        self.look_image = ImageTk.PhotoImage(self.now_image)  # 显示的图片
        self.instance_image = master.now_image_output[0]['instances']  # 实例分割的结果
        self.none_image = ImageTk.PhotoImage(Image.new('RGB', (0, 0)))
        # 左边的图片栏
        self.l_frame = tk.LabelFrame(self, text="图片")
        self.scrollbar_x = tk.Scrollbar(self.l_frame, orient=tk.HORIZONTAL)  # 滚动条x
        self.scrollbar_y = tk.Scrollbar(self.l_frame, orient=tk.VERTICAL)  # 滚动条y
        self.image_canvas = tk.Canvas(self.l_frame,
                                      xscrollcommand=self.scrollbar_x.set,
                                      yscrollcommand=self.scrollbar_y.set, )
        self.image_id = self.image_canvas.create_image(0, 0, anchor="nw",
                                                       image=self.look_image)
        self.image_canvas.configure(scrollregion=(0, 0, self.look_image.width(),
                                                  self.look_image.height()))  # 更新Canvas的滚动区域
        self.image_canvas.update()  # 更新canvas以显示新图片
        self.scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)  # 靠下,拉满x
        self.scrollbar_x.config(command=self.image_canvas.xview)
        self.scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)  # 靠右,拉满y
        self.scrollbar_y.config(command=self.image_canvas.yview)
        self.image_canvas.pack(fill=tk.BOTH, expand=True)
        self.l_frame.grid(row=0, column=0, sticky="wesn")
        # 右边的操作栏
        self.r_frame = tk.LabelFrame(self, text="操作")
        self.r_frame.grid(row=0, column=1, sticky="wesn")
        self.display_mask = tk.IntVar()  # 显示掩膜
        self.display_mask.set(1)

        # 分辨率+-,选择保留的实例,背景虚化,背景替换纯色
        # 缩放系数滑条
        def Update_Scale_Value(event):
            # 获取滑条当前的值
            current_value = self.scale_factor_scale.get()
            self.scale_factor = current_value

        def Change_Scale(i):  # 缩放按钮的回调
            new_scale_factor = self.scale_factor + i
            if new_scale_factor < self.scale_factor_scope[0] or new_scale_factor > self.scale_factor_scope[1]:
                return
            else:
                self.scale_factor = new_scale_factor
                self.scale_factor_scale.set(self.scale_factor)
                self.Scale_Image()

        img_size = max(self.image.size)  # 左边图片栏近似看成750大小的正方形,
        self.scale_factor = round((750 / img_size) / 0.05) * 0.05  # 初始缩放系数,让图片正正好显示
        self.scale_factor_scope = [self.scale_factor, self.scale_factor * 6]  # 动态比例,最小让图片正好显示,最大放大六倍,防止卡顿
        if self.scale_factor_scope[0] > 1:  # 缩放比范围微调
            self.scale_factor_scope[0] = 1
        if self.scale_factor_scope[1] < 1:
            self.scale_factor_scope[1] = 1
        self.scale_factor_scale = tk.Scale(self.r_frame, from_=self.scale_factor_scope[0],
                                           to=self.scale_factor_scope[1],
                                           resolution=0.05,
                                           orient='horizontal', command=Update_Scale_Value)
        self.scale_factor_scale.set(self.scale_factor)  # 设置初始值
        self.scale_factor_scale.bind('<ButtonRelease-1>', self.Scale_Image)  # 松开才执行图片修改,防止大量修改图片卡顿
        self.image_up_b = tk.Button(self.r_frame, text="+", command=lambda i=0.05: Change_Scale(i))
        self.image_down_b = tk.Button(self.r_frame, text="-", command=lambda i=-0.05: Change_Scale(i))
        self.image_down_b.grid(row=0, column=0)
        self.scale_factor_scale.grid(row=0, column=1, columnspan=2)
        self.image_up_b.grid(row=0, column=3)

        # 选择保存的实例
        def Select_Instance(i):  # <和>按钮的回调
            if self.now_instances < 0:
                return
            if i == 1:
                if self.now_instances + 1 < self.instances_num:
                    self.now_instances += 1
                else:
                    self.now_instances = 0
            else:
                if self.now_instances - 1 > -1:
                    self.now_instances -= 1
                else:
                    self.now_instances = self.instances_num - 1
            self.Select_Instance()  # 按当前选中的实例设置gui

        def Change_Checkbutton():  # 复选框回调
            if self.now_instances < 0:
                return
            if self.hold.get() == 1:  # 勾选了
                self.hold_list[self.now_instances] = True
            else:  # 没有勾选
                self.hold_list[self.now_instances] = False
            if not self.now_mode.get() == 0:
                self.Goto_Modle()
            self.Select_Masks(self.now_instances)

        self.instances_num = len(self.instance_image)  # 总实例数目
        self.hold_list = [False] * self.instances_num  # 每个实例是否保留
        self.masks = []  # 每个实例的掩膜
        self.look_masks = []  # 显示的掩膜
        self.masks_imgid = []  # 每个掩膜绘制后索引的id
        self.instances_l_b = tk.Button(self.r_frame, text="<", command=lambda i=0: Select_Instance(i))
        self.instances_r_b = tk.Button(self.r_frame, text=">", command=lambda i=1: Select_Instance(i))
        self.hold = tk.IntVar()  # 是否保留该实例
        self.hold.set(0)
        self.hold_b = tk.Checkbutton(self.r_frame, text="保留", variable=self.hold, command=Change_Checkbutton)  # 保留实例选择框
        self.instances_position = [tk.StringVar(), tk.StringVar(), tk.StringVar()]  # 实例位置关联变量,实例序号,实例起始点,实例结束点
        self.instances_position_l = [tk.Label(self.r_frame, textvariable=self.instances_position[0], anchor="w"),
                                     tk.Label(self.r_frame, textvariable=self.instances_position[1], anchor="w"),
                                     tk.Label(self.r_frame, textvariable=self.instances_position[2],
                                              anchor="w")]  # 三个标签来显示当前选中的实例位置
        if not self.instances_num == 0:  # 只有检测出实例的才执行,否则不执行
            self.now_instances = 0
            self.Select_Instance()
        else:
            self.now_instances = -1

        def Display_Mask():
            if self.display_mask.get() == 0:  # 不显示:
                self.image_canvas.delete("Select")
                for i in range(self.instances_num):
                    self.image_canvas.itemconfig(self.masks_imgid[i], state="hidden")
            else:  # 显示:
                self.show_Select()
                for i in range(self.instances_num):
                    self.image_canvas.itemconfig(self.masks_imgid[i], state="normal")
                self.image_canvas.update()

        self.display_mask_b = tk.Checkbutton(self.r_frame, text="显示掩膜", variable=self.display_mask,
                                             command=Display_Mask)  # 保留实例选择框
        self.instances_l_b.grid(row=1, column=0)
        self.hold_b.grid(row=1, column=1, columnspan=2)
        self.instances_r_b.grid(row=1, column=3)
        self.display_mask_b.grid(row=2, column=0, columnspan=3)
        tk.Label(self.r_frame, text=f"总实例数:{self.instances_num}").grid(row=2, column=2, columnspan=2)
        self.instances_position_l[0].grid(row=3, column=0, columnspan=4)
        self.instances_position_l[1].grid(row=4, column=0, columnspan=4)
        self.instances_position_l[2].grid(row=5, column=0, columnspan=4)

        # 选择模式,正常模式就是正常进行实例选择
        self.now_mode = tk.IntVar()
        self.now_mode.set(0)
        # 正常模式
        self.normal_mode = tk.Radiobutton(self.r_frame, text='正常模式', variable=self.now_mode, value=0,
                                          command=self.Goto_Modle)

        # 背景模糊
        def Update_Ambiguity_Value(event):
            # 获取滑条当前的值
            current_value = self.ambiguity_scale.get()
            self.ambiguity = current_value

        def Whether_Goto_Modle(i):
            if i == self.now_mode.get():
                self.Goto_Modle()

        self.background_blurred = tk.Radiobutton(self.r_frame, text='背景模糊', variable=self.now_mode, value=1,
                                                 command=self.Goto_Modle)
        self.ambiguity = 20  # 模糊度
        self.ambiguity_scale = tk.Scale(self.r_frame, from_=1, to=100, resolution=0.5, orient='horizontal',
                                        command=Update_Ambiguity_Value)
        self.ambiguity_scale.set(self.ambiguity)  # 设置初始值
        self.ambiguity_scale.bind('<ButtonRelease-1>', lambda e, i=1: Whether_Goto_Modle(i))  # 松开才执行

        # 背景替换纯色
        def select_color():
            # 颜色选择对话框
            color = colorchooser.askcolor()
            if color:
                _, self.background_color = color
            Whether_Goto_Modle(2)

        self.background_color = "#000000"
        self.background_replacement = tk.Radiobutton(self.r_frame, text='背景纯色', variable=self.now_mode, value=2,
                                                     command=self.Goto_Modle)
        # 选择替换纯色的颜色
        self.choose_color = tk.Button(self.r_frame, text="选择颜色", command=select_color)

        # 保存当前图片
        def Save_Img():
            self.master.Save_Image(img=self.now_image)

        self.save_b = tk.Button(self.r_frame, text="保存当前图片", command=Save_Img)
        self.open_save_path = tk.Button(self.r_frame, text="打开默认保存位置",
                                        command=self.master.Open_Default_Save_Path)  # 打开默认保存位置

        self.normal_mode.grid(row=6, column=0, columnspan=2)
        self.background_blurred.grid(row=7, column=0, columnspan=2)
        self.ambiguity_scale.grid(row=7, column=2, columnspan=2)
        self.background_replacement.grid(row=8, column=0, columnspan=2)
        self.choose_color.grid(row=8, column=2, columnspan=2)
        self.save_b.grid(row=10, column=0, columnspan=4)
        self.open_save_path.grid(row=11, column=0, columnspan=4)

        for i in range(12):
            self.r_frame.rowconfigure(i, weight=1)
        self.r_frame.rowconfigure(9, weight=5)
        for i in range(4):
            self.r_frame.columnconfigure(i, weight=1)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=5)
        self.columnconfigure(1, weight=1)

        self.Generating_Masks()  # 生成掩膜
        self.Scale_Image()  # 显示图片

    # 修改image_canvas显示的图片
    def Change_Img(self, img):
        self.look_image = ImageTk.PhotoImage(img)  # 显示的图片
        self.image_canvas.itemconfig(self.image_id, image=self.look_image)
        self.image_canvas.configure(scrollregion=(0, 0, self.look_image.width(),
                                                  self.look_image.height()))  # 更新Canvas的滚动区域

    def Scale_Image(self, event=None):  # 根据缩放系数显示图片以及掩膜
        self.Change_Img(self.master.Zoom_Image(self.now_image, proportion=self.scale_factor))
        if self.display_mask.get() == 0:  # 不显示掩膜
            pass
        else:
            for i in range(self.instances_num):
                self.Select_Masks(i)  # 显示掩膜
            self.show_Select()  # 显示当前实例红框

    def Select_Masks(self, i):  # 更新掩膜,如果显示按比例生成要显示的掩膜,并显示出来,否则替换显示为空白图片
        if self.hold_list[i]:  # 该实例要显示
            self.look_masks[i] = ImageTk.PhotoImage(  # 按当前比例生成掩膜
                self.master.Zoom_Image(self.masks[i], proportion=self.scale_factor))
            self.image_canvas.itemconfig(self.masks_imgid[i], image=self.look_masks[i], tag="mask")  # 显示掩膜
        else:
            self.image_canvas.itemconfig(self.masks_imgid[i], image=self.none_image)

    def Select_Instance(self):  # 按当前选中的实例设置gui
        pred_boxes = self.instance_image[self.now_instances].get("pred_boxes").tensor[0]
        self.instances_position[0].set(f"当前实例:{self.now_instances + 1}")
        self.instances_position[1].set(f"起始坐标:{int(pred_boxes[0])},{int(pred_boxes[1])}")
        self.instances_position[2].set(f"结束坐标:{int(pred_boxes[2])},{int(pred_boxes[3])}")
        if self.hold_list[self.now_instances]:
            self.hold.set(1)
        else:
            self.hold.set(0)
        self.show_Select()

    def show_Select(self):  # 显示当前选中的实例,一个外面的红边框
        if self.now_instances >= 0:
            self.image_canvas.delete("Select")
            pred_boxes = self.instance_image[self.now_instances].get("pred_boxes").tensor[0]
            self.image_canvas.create_rectangle(int(pred_boxes[0] * self.scale_factor),
                                               int(pred_boxes[1] * self.scale_factor),
                                               int(pred_boxes[2] * self.scale_factor),
                                               int(pred_boxes[3] * self.scale_factor), outline="red", fill=None,
                                               tag="Select")

    # 生成每个实例的掩膜
    def Generating_Masks(self):
        for i in range(self.instances_num):  # 每个实例生成一个掩膜
            colour = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), 192)
            mask_array = np.array(self.instance_image[i].get("pred_masks")[0].to("cpu"))
            mask_image = Image.fromarray(mask_array * 255, mode='L')  # 创建一个PIL灰度图像
            # 创建一个与mask_image相同大小的透明图像
            transparent_image = Image.new('RGBA', mask_image.size, (0, 0, 0, 0))
            draw = ImageDraw.Draw(transparent_image)
            for y, row in enumerate(mask_array):
                for x, value in enumerate(row):
                    if value:
                        draw.point((x, y), fill=colour)
            self.masks.append(transparent_image)  # 半透明随机颜色掩膜

            # self.look_masks.append(  # 用于显示的掩膜
            #     ImageTk.PhotoImage(self.master.Zoom_Image(self.masks[i], proportion=self.scale_factor)))
            self.look_masks.append(self.none_image)  # 用于显示的掩膜,初始都没有,所以整个空图片
            self.masks_imgid.append(
                self.image_canvas.create_image(0, 0, anchor="nw", image=self.look_masks[i], tag="mask"))  # 显示掩膜

    def Goto_Modle(self):  # 根据当前模式启用或者禁用按钮,并修改self.now_image(当前处理的图片)
        if self.now_mode.get() == 0:
            self.now_image = self.image  # 当前处理的图片
            self.Scale_Image()
        elif self.now_mode.get() == 1:
            # 给整张图片虚化,然后将当前实例部分保持不变
            self.now_image = self.image.filter(ImageFilter.GaussianBlur(radius=self.ambiguity))
            self.Show_Instances()
            self.Scale_Image()
        elif self.now_mode.get() == 2:
            self.now_image = Image.new('RGB', self.image.size, color=self.background_color)
            self.Show_Instances()
            self.Scale_Image()

    def Show_Instances(self):  # 显示实例,就是在当前图片上将该实例拓印下来
        array_img = np.array(self.image)
        array_now_img = np.array(self.now_image)
        for i in range(self.instances_num):
            if self.hold_list[i]:
                mask_array = self.instance_image[i].get("pred_masks")[0].to("cpu")
                # 将原图中对应为True的像素赋值给当前处理图片的对应位置
                array_now_img[mask_array] = array_img[mask_array]
        # 将NumPy数组转回PIL图像
        self.now_image = Image.fromarray(array_now_img)

screen_recognition_window.py

# 屏幕识别窗口,还有一个选择屏幕区域的窗口和截取屏幕转为流的类
import time
import tkinter as tk  # gui用
from threading import Thread
from tkinter import messagebox
import tkinter.filedialog  # 文件相关窗口

import cv2
import numpy as np
from PIL import Image, ImageTk, ImageDraw
from mss import mss


class ScreenRecognitionWindow(tk.Toplevel):
    def __init__(self, master, ip=False):
        super().__init__(master)  # 父类调用,
        self.master = master
        self.grab_set()  # 独占焦点

        if ip:
            self.ip = f"http://{self.master.down_frame_screen.ip_e[1].get()}:{self.master.down_frame_screen.ip_e[2].get()}@{self.master.down_frame_screen.ip_e[0].get()}/"
            self.title("网络摄像头识别")
            self.capture = cv2.VideoCapture(self.ip)
            # 尝试读取一帧
            ret, img = self.capture.read()
            if not ret:
                tk.messagebox.showwarning(title='警告!', message='无法从摄像头中读取图片')
                self.destroy()
                return
            self.monitor = {"top": 0
                , "left": 0
                , "width": img.shape[1]
                , "height": img.shape[0]}
        else:
            self.ip = None
            self.title("屏幕识别")
            # 截取部分大小
            self.monitor = {"top": self.master.up_frame_screen.position[0].get()
                , "left": self.master.up_frame_screen.position[1].get()
                , "width": self.master.up_frame_screen.position[2].get()
                , "height": self.master.up_frame_screen.position[3].get()}
        self.processing_mode = self.master.up_frame_screen.processing_mode.get()  # 处理方式
        self.scale_factor = self.master.up_frame_screen.scale_factor  # 缩放系数
        self.new_width = int(self.monitor["width"] * self.scale_factor)  # 缩放后的大小
        self.new_height = int(self.monitor["height"] * self.scale_factor)
        self.now_width = self.monitor["width"]
        self.now_height = self.monitor["height"]
        # 设置大小和位置(设置为截取部分的大小)
        self.geometry("%dx%d+0+0" % (self.monitor["width"], self.monitor["height"]))
        self.canvas = tkinter.Canvas(self, width=self.monitor["width"], height=self.monitor["height"], )
        self.image_id = self.canvas.create_image(0, 0, anchor="nw",
                                                 image=ImageTk.PhotoImage(Image.new('RGB', (0, 0))))  # 先不放图片
        self.canvas.pack(fill=tk.BOTH, expand=True)
        self.bind("<Configure>", self.On_Resize)  # 绑定窗口大小改变事件
        self.bind('<Button-1>', self.Button_Click)  # 绑定鼠标左键点击事件
        self.start_up = False

    def destroy(self):
        super().destroy()
        if not self.ip is None:
            self.capture.release()

    def Button_Click(self, event):
        if self.start_up:
            return
        else:
            self.start_up = True
            self.Handle()

    def On_Resize(self, event):
        # 计算新的宽度和高度,保持屏幕截图的比例
        new_width = event.width
        new_height = event.height
        # print(new_width,new_height,self.monitor["width"],self.monitor["height"])
        if new_width == self.now_width and new_height == self.now_height:
            return
        proportion = min(new_width / self.monitor["width"], new_height / self.monitor["height"])
        self.now_width = int(proportion * self.monitor["width"])
        self.now_height = int(proportion * self.monitor["height"])
        self.geometry(f"{self.now_width}x{self.now_height}+{self.winfo_x()}+{self.winfo_y()}")

    def Handle(self):
        if self.ip is None:
            if self.processing_mode == 0:
                sct = mss()  # 创建一个屏幕捕获对象
                while True:
                    screenshot = np.uint8(sct.grab(self.monitor))  # 捕获屏幕截图
                    screenshot = cv2.resize(screenshot, (self.new_width, self.new_height))  # 修改大小
                    screenshot = cv2.cvtColor(screenshot, cv2.COLOR_RGB2BGR)  # 将截图转换为OpenCV格式(BGR)
                    _, visualized_output = self.master.visualization.run_model_image(screenshot)
                    self.canvas.now_image = ImageTk.PhotoImage(
                        Image.fromarray(cv2.resize(visualized_output.get_image(), (self.now_width, self.now_height))))
                    self.canvas.itemconfig(self.image_id, image=self.canvas.now_image)
                    self.canvas.update()  # 更新canvas以显示新图片
                    if not self.winfo_exists():
                        self.start_up = False
                        break
                    # time.sleep(0.01)

            elif self.processing_mode == 1:
                screen = Screen(self.monitor, self.scale_factor)
                for vis in self.master.visualization.run_model_video(screen, screen=True):
                    self.canvas.now_image = ImageTk.PhotoImage(Image.fromarray(  # 调整为窗口大小
                        cv2.resize(vis, (self.now_width, self.now_height))))
                    self.canvas.itemconfig(self.image_id, image=self.canvas.now_image)
                    self.canvas.update()  # 更新canvas以显示新图片
                    if not self.winfo_exists():
                        self.start_up = False
                        break
                    # time.sleep(0.01)
        else:
            screen = Screen(self.monitor, self.scale_factor, ip=self.capture)
            if self.processing_mode == 0:
                while True:
                    ret, img = screen.next()
                    if not ret:  # 没读取成功图片
                        self.start_up = False
                        break
                    _, visualized_output = self.master.visualization.run_model_image(img)
                    self.canvas.now_image = ImageTk.PhotoImage(
                        Image.fromarray(cv2.resize(visualized_output.get_image(), (self.now_width, self.now_height))))
                    self.canvas.itemconfig(self.image_id, image=self.canvas.now_image)
                    self.canvas.update()  # 更新canvas以显示新图片
                    if not self.winfo_exists():
                        self.start_up = False
                        break
                    # time.sleep(0.01)
            elif self.processing_mode == 1:
                for vis in self.master.visualization.run_model_video(screen, screen=True):
                    self.canvas.now_image = ImageTk.PhotoImage(Image.fromarray(  # 调整为窗口大小
                        cv2.resize(vis, (self.now_width, self.now_height))))
                    self.canvas.itemconfig(self.image_id, image=self.canvas.now_image)
                    self.canvas.update()  # 更新canvas以显示新图片
                    if not self.winfo_exists():
                        self.start_up = False
                        break
                    # time.sleep(0.01)
            screen.Stop()


class Screen(object):  # 一个获取屏幕的对象,流式
    def __init__(self, monitor=None, scale_factor=1, ip=None):  # 初始化
        if monitor is None:
            monitor = {"top": 0, "left": 0, "width": 1920, "height": 1080}
        self.ip = ip
        if ip is None:
            self.sct = mss()  # 创建一个屏幕捕获对象
            self.monitor = monitor  # 定义捕获的屏幕区域
        else:
            # 预分配缓冲区
            self.buffer = []
            self.buffer_size = 8
            self.get_ip = Thread(target=self.Get_IP_Img)
            self.get_ip.start()
        self.scale_factor = scale_factor  # 缩放系数
        self.new_width = int(monitor["width"] * scale_factor)
        self.new_height = int(monitor["width"] * scale_factor)
        self.stop=False

    def Get_IP_Img(self):  # 一直维持一个大小为self.buffer_size的缓冲区,
        while True:
            try:
                while len(self.buffer) < self.buffer_size:
                    ret, img = self.ip.read()
                    self.buffer.append(cv2.cvtColor(img, cv2.COLOR_RGB2BGR))
                if self.buffer:
                    self.buffer.pop(0)  # 删除最老的帧
                if self.stop:
                    break
            except:
                break

    def Stop(self):
        self.stop=True

    def next(self):
        if self.ip is None:
            try:
                screenshot = np.uint8(self.sct.grab(self.monitor))  # 捕获屏幕截图
                screenshot = cv2.resize(screenshot, (self.new_width, self.new_height))  # 修改大小
                screenshot = cv2.cvtColor(screenshot, cv2.COLOR_RGB2BGR)  # 将截图转换为OpenCV格式(BGR)
            except:
                return False, None
            return True, screenshot
        else:
            try:
                if self.buffer:
                    img = self.buffer.pop(0)
                    screenshot = cv2.resize(img, (self.new_width, self.new_height))
                    return True, screenshot
                else:
                    time.sleep(0.1)
                    if self.buffer:
                        img = self.buffer.pop(0)
                        screenshot = cv2.resize(img, (self.new_width, self.new_height))
                        return True, screenshot
                    else:
                        self.Stop()
                        return False, None
            except:
                self.Stop()
                return False, None


class SelectScreenArea(tk.Toplevel):  # 创建一个透明窗口,然后再在这个透明窗口上进行选择操作
    # 仿照类似qq截屏的样子,想法是截屏+遮罩,遮罩用四个半透明矩形处理,只用更改右边和下面矩形的大小就行
    def __init__(self, master):
        super().__init__(master)  # 父类调用,
        self.master = master
        sct = mss()  # 在窗口显示出来之前截屏
        monitors = sct.monitors  # # 获取所有显示器的信息
        self.m_width = monitors[0]["width"]
        self.m_height = monitors[0]["height"]
        # print(monitors)
        self.screenshot = ImageTk.PhotoImage(
            Image.fromarray(cv2.cvtColor(np.uint8(sct.grab(monitors[0])), cv2.COLOR_BGR2RGB)))

        self.grab_set()  # 独占焦点
        # self.attributes("-fullscreen", True)  # 设置全屏,这只能截取第一个屏幕(我是双屏)
        self.overrideredirect(True)  # 隐藏边框
        self.geometry("{0}x{1}+0+0".format(self.m_width, self.m_height))
        self.attributes("-topmost", True)  # 设置窗口在最上层
        self.configure(bg="blue")  # 蓝色背景,看一看
        # 一个有颜色画布遮挡住全部屏幕
        self.canvas = tkinter.Canvas(self, width=self.m_width, height=self.m_height, )
        self.canvas.pack(fill=tk.BOTH)
        self.canvas.create_image(0, 0, anchor="nw", image=self.screenshot)  # 整个屏幕的截图并放在画布上
        self.canvas.update()  # 更新canvas以显示新图片
        self.canvas.create_text(0, 0, text="左键框选窗口,右键确定,esc退出", anchor="nw", font=("Arial", 20), fill="black")
        self.image_masking = [ImageTk.PhotoImage(Image.new('RGB', (0, 0)))] * 4  # 四个遮罩
        self.bind('<Button-1>', self.Button_Click)  # 绑定鼠标左键点击事件
        self.bind('<B1-Motion>', self.Button_Loosen)  # 绑定鼠标左键点击移动事件
        self.bind('<ButtonRelease-1>', self.Button_Movex)  # 绑定鼠标左键点击释放事件
        self.bind('<Button-3>', self.Button_Click_3)  # 绑定鼠标右键点击事件
        self.bind('<Escape>', lambda event: self.destroy())  # 绑定Esc按键退出事件,不返回输出
        self.start = [0, 0]
        self.end = [0, 0]

    def Button_Click(self, event):  # 按下
        # 本来的考虑是按下就能防止最左边和最上面框,并且不用改,没想到还有往左上角拖动这一操作
        self.start[0], self.start[1] = [event.x, event.y]  # 保存开始位置
        self.Set_Masking(event.x, event.y)  # 设置遮罩

    def Button_Loosen(self, event):  # 移动
        self.Set_Masking(event.x, event.y)  # 设置遮罩

    def Button_Movex(self, event):  # 松开
        self.end[0], self.end[1] = [event.x, event.y]  # 保存结束位置

    def Set_Masking(self, x, y):  # 修改掩膜(遮罩)
        if self.start[0] > x:
            max_x = self.start[0]
            min_x = x
        else:
            min_x = self.start[0]
            max_x = x
        if self.start[1] > y:
            max_y = self.start[1]
            min_y = y
        else:
            min_y = self.start[1]
            max_y = y
        size_x = self.m_width
        size_y = self.m_height
        self.canvas.delete("masking")  # 删除之前的遮罩(不加也会没有,但感觉不安全)
        self.image_masking[0] = ImageTk.PhotoImage(Image.new('RGBA', (min_x, size_y), (0, 0, 0, 128)))  # 左遮罩
        self.canvas.create_image(0, 0, anchor="nw", image=self.image_masking[0], tag="masking")
        self.image_masking[1] = ImageTk.PhotoImage(Image.new('RGBA', (max_x - min_x, min_y), (0, 0, 0, 128)))  # 上遮罩
        self.canvas.create_image(min_x, 0, anchor="nw", image=self.image_masking[1], tag="masking")
        self.image_masking[2] = ImageTk.PhotoImage(Image.new('RGBA', (size_x - max_x, size_y), (0, 0, 0, 128)))  # 右遮罩
        self.canvas.create_image(max_x, 0, anchor="nw", image=self.image_masking[2], tag="masking")
        self.image_masking[3] = ImageTk.PhotoImage(Image.new('RGBA', (max_x - min_x, size_y - max_y), (0, 0, 0, 128)))
        self.canvas.create_image(min_x, max_y, anchor="nw", image=self.image_masking[3], tag="masking")  # 下遮罩
        self.canvas.update()  # 更新canvas以显示新图片

    def Button_Click_3(self, event):  # 右键保存输入
        if self.start[0] > self.end[0]:
            max_x = self.start[0]
            min_x = self.end[0]
        else:
            min_x = self.start[0]
            max_x = self.end[0]
        if self.start[1] > self.end[1]:
            max_y = self.start[1]
            min_y = self.end[1]
        else:
            min_y = self.start[1]
            max_y = self.end[1]
        self.master.up_frame_screen.position[0].set(min_y)
        self.master.up_frame_screen.position[1].set(min_x)
        self.master.up_frame_screen.position[2].set(max_x - min_x)
        self.master.up_frame_screen.position[3].set(max_y - min_y)
        self.destroy()


if __name__ == '__main__':
    main_window = tk.Tk()
    SelectScreenArea(main_window)
    main_window.up_frame_screen = tk.LabelFrame(main_window, text="屏幕")
    main_window.up_frame_screen.position = [tk.IntVar(), tk.IntVar(), tk.IntVar(), tk.IntVar()]
    main_window.mainloop()  # 开启主循环,让窗口处于显示状态

set_up_window.py

# 设置窗口类,打开一个设置窗口
import tkinter as tk  # gui用
from tkinter import messagebox
import tkinter.filedialog  # 文件相关窗口


class SetUpWindow(tk.Toplevel):
    def __init__(self, master):
        super().__init__(master)  # 父类调用,
        self.master = master
        self.title("设置")
        self.grab_set() # 独占焦点
        # 设置大小和位置(相对于父窗口左上角)
        self.geometry("%dx%d+%d+%d" % (640, 192, master.winfo_x() + 128, master.winfo_y() + 128))
        self.resizable(False, False)  # 禁止缩放
        # 仅仅先设置三个:默认模型位置,默认配置文件位置,默认保存位置,其他的什么并行,先不管。
        self.default_path = [tk.StringVar(), tk.StringVar(), tk.StringVar()]  # 文本框关联默认位置变量
        self.default_path[0].set(master.default_path[0])
        self.default_path[1].set(master.default_path[1])
        self.default_path[2].set(master.default_path[2])
        # 上半部分===================================================
        self.data_frame = tk.LabelFrame(self, text="默认路径", )  # 一个标签框架,装除了保存取消按钮以外所有的设置项
        data_len = 3
        # 三个位置,每个给三个控件:标签,输入框,选择按钮。
        self.data_dick = {}
        self.data_dick["save_model"] = [tk.Label(self.data_frame, text="默认模型位置"),
                                        tk.Entry(self.data_frame, textvariable=self.default_path[0]),
                                        tk.Button(self.data_frame, text="请选择文件", command=self.Select_File)]  # 默认模型位置控件
        self.data_dick["save_yaml"] = [tk.Label(self.data_frame, text="模型配置位置"),
                                       tk.Entry(self.data_frame, textvariable=self.default_path[1]),
                                       tk.Button(self.data_frame, text="请选择文件", command=self.Select_Yaml)]  # 模型配置位置控件
        self.data_dick["save_path"] = [tk.Label(self.data_frame, text="默认保存位置"),
                                       tk.Entry(self.data_frame, textvariable=self.default_path[2]),
                                       tk.Button(self.data_frame, text="选择文件夹", command=self.Select_Folder)]  # 默认保存位置控件

        def Implement_Grid(list, row):  # 将控件放好位置
            list[0].grid(row=row, column=0, )
            list[1].grid(row=row, column=1, )
            list[2].grid(row=row, column=2, )

        for i, j in zip(self.data_dick, range(data_len)):
            Implement_Grid(self.data_dick[i], j)
        for i in range(3):  # 调整缩放比
            self.data_frame.columnconfigure(i, weight=1)
            self.data_frame.rowconfigure(i, weight=1)
        self.data_frame.grid(row=0, column=0, sticky="wesn")

        # 下半部分===================================================
        self.button_frame = tk.LabelFrame(self, text="完成", )  # 一个标签框架,装除了保存取消按钮以外所有的设置项
        save_button = tk.Button(self.button_frame, text="保存", command=self.Save_Settings)
        cancel_button = tk.Button(self.button_frame, text="取消", command=self.destroy)  # 直接关了,不管那么多

        save_button.grid(row=0, column=0, )
        cancel_button.grid(row=0, column=1, )
        self.button_frame.grid(row=1, column=0, sticky="wesn")

        self.rowconfigure(0, weight=data_len)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)

    def Save_Settings(self):  # 保存设置
        self.master.Change_Path_Data([i.get() for i in self.default_path])
        self.master.Initialize_Model()
        messagebox.showinfo("保存成功", "设置已保存")
        self.destroy()  # 关闭窗口

    def Select_File(self):  # 选择文件
        file_path = tk.filedialog.askopenfilename(filetypes=[("实例分割的模型文件", '*.pth')])  # 文件选择对话框
        if file_path.strip() != '':  # 是空的时候,往往没有选择,直接关闭窗口
            self.default_path[0].set(file_path.strip())  # 将文本框值改变为新路径

    def Select_Yaml(self):  # 选择文件
        file_path = tk.filedialog.askopenfilename(filetypes=[("实例分割的配置文件", '*.yaml')])  # 文件选择对话框
        if file_path.strip() != '':  # 是空的时候,往往没有选择,直接关闭窗口
            self.default_path[1].set(file_path.strip())  # 将文本框值改变为新路径

    def Select_Folder(self):  # 选择文件夹
        file_path = tk.filedialog.askdirectory()
        if file_path.strip() != '':  # 是空的时候,往往没有选择,直接关闭窗口
            self.default_path[2].set(file_path.strip())  # 将文本框值改变为新路径

因为我电脑没摄像头,所以没设置电脑摄像头功能。

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AdelaiDet是Detectron2一个扩展包,用于目标检测任务。它包含了一些新的anchor-free模型,如FCOS,并且支持COCO格式的数据集。要训练自己的数据集,你需要按照以下步骤进行操作: 1. 准备好自己的数据集,包括标注文件和图像。标注文件可以是COCO格式的JSON文件或者VOC格式的XML文件。确保标注文件中的类别id从1开始,并且与图像路径对应。 2. 安装好Detectron2AdelaiDet,并熟悉其安装和使用方法。你可以参考官方的安装文档和入门指南。 3. 配置数据集,可以参考AdelaiDet的GitHub上的datasets/readme.md文件,了解如何使用内置数据集。你需要注册你自己的数据集,指定标注文件和图像路径等信息。 4. 使用以下命令克隆Detectron2AdelaiDet的GitHub仓库,并安装AdelaiDet: ``` git clone https://github.com/facebookresearch/detectron2.git cd detectron2 git checkout -f 9eb4831 cd .. python -m pip install -e detectron2 git clone https://github.com/aim-uofa/AdelaiDet.git cd AdelaiDet python setup.py build develop ``` 5. 根据你的需求修改配置文件,例如训练参数、模型架构等。你可以参考AdelaiDet的GitHub仓库中的示例配置文件。 6. 运行训练脚本,指定配置文件和数据集名称。例如: ``` python tools/train_net.py --config-file configs/FCOS-Detection/R_50_1x.yaml --num-gpus 8 DATASETS.TRAIN "('your_dataset_name',)" OUTPUT_DIR "outputs/your_experiment_name" ``` 其中,`--config-file`指定配置文件路径,`--num-gpus`指定使用的GPU数量,`DATASETS.TRAIN`指定训练数据集名称,`OUTPUT_DIR`指定输出目录。 希望以上步骤对你训练自己的数据集有所帮助。如果有任何问题,请在评论中留言,我会尽力帮助你。 #### 引用[.reference_title] - *1* *2* *3* [[Detectron2]使用Detectron2/AdelaiDet训练自己的数据集](https://blog.csdn.net/weixin_43823854/article/details/108980188)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值