乐于分享
好东西不私藏

AI人物识别软件

AI人物识别软件

一、软件功能

  1. 人物识别
    :基于参考照片,在指定文件夹中查找相似人物
  2. 支持多种文件格式
    :支持JPGJPEGPNGBMPGIF等图片格式,以及MP4AVIMOVWMV等视频格式
  3. ASCII路径支持
    :支持包含中文等非ASCII字符的文件路径
  4. 可视化界面
    :提供直观的图形用户界面,操作简单
  5. 进度显示
    :实时显示处理进度和结果
  6. 结果排序
    :按相似度排序并显示最相似的结果

二、安装方法

  1. 下载AI人物识别.exe文件
  2. 双击运行即可,无需安装

三、使用步骤

  1. 选择文件夹
    :点击浏览按钮,选择要搜索的文件夹
  2. 选择参考照片
    :点击选择按钮,选择包含目标人物的参考照片
  3. 处理完整视频
    (可选):如果需要处理视频文件的完整内容,勾选处理完整视频选项
  4. 查找相似人物
    :点击查找相似人物按钮开始搜索
  5. 查看结果
    :处理完成后,结果会显示在识别结果表格中,并按相似度排序
  6. 查看预览
    :点击表格中的结果,可以在右侧预览窗口中查看图片或视频帧

四、注意事项

  1. 参考照片要求
    • 参考照片应清晰显示人物面部
    • 面部应占据照片的大部分区域
    • 光线应充足,避免过暗或过亮
    • 面部应正面朝向摄像头
    • 避免遮挡面部的物品,如帽子、墨镜等
  2. 性能注意事项
    • 处理大量文件或视频可能需要较长时间
    • 处理完整视频会消耗更多系统资源和时间
    • 建议在处理大量文件时关闭其他占用资源的程序
  3. 系统要求
    • Windows 10
      或更高版本
    • 至少4GB内存
    • 足够的存储空间
    • 支持OpenCV的硬件环境
  4. 故障排除
    • 如果无法读取参考照片,检查文件路径是否包含非ASCII字符
    • 如果检测不到人脸,尝试调整参考照片或使用更清晰的照片
    • 如果程序无响应,可能是正在处理大量文件,请耐心等待
    • 如果出现错误,检查控制台输出以获取详细信息
  5. 其他注意事项
    • 本软件仅用于个人和非商业用途
    • 处理视频文件时,会提取关键帧进行分析,可能会遗漏某些帧
    • 相似度计算基于简单的直方图比较,可能不是最精确的方法
    • 对于分辨率较低的图片,识别准确率可能会降低

五、常见问题

Q: 为什么参考照片中未检测到人脸?

A: 可能的原因包括:照片中人脸不清晰、光线过暗或过亮、人脸被遮挡、人脸角度过大等。建议使用清晰、正面、光线充足的照片。

Q: 为什么很多照片显示相似度为0

A: 可能是因为这些照片中没有检测到人脸,或者检测到的人脸与参考照片差异较大。

Q: 处理视频文件时,为什么只显示部分帧?

A: 默认情况下,软件会提取视频的关键帧进行分析,以提高处理速度。如果需要处理所有帧,请勾选处理完整视频选项。

Q: 为什么软件运行速度较慢?

A: 处理大量文件或视频需要一定的时间,特别是在处理完整视频时。建议在处理大量文件时关闭其他占用资源的程序。

Q: 为什么软件无法读取某些图片?

A: 可能是因为图片格式不支持,或者文件路径包含非ASCII字符。本软件已经优化了对非ASCII路径的支持,但某些特殊情况可能仍然存在问题。

下载


链接: https://pan.baidu.com/s/1IHOJ43ZmMy9h67PtXqg_CQ 

提取码: dqi6

操作


已关注

关注

重播 分享

代码


import tkinter as tkfrom tkinter import filedialog, messagebox, ttkimport cv2import osimport threadingimport timefrom PIL import Image, ImageTkimport numpy as npclass AIRecognitionApp:def __init__(self, root):self.root = rootself.root.title("AI人物识别 - 开水拌饭")获取屏幕大小screen_width = root.winfo_screenwidth()        screen_height = root.winfo_screenheight()设置窗口大小为屏幕大小self.root.geometry(f"{screen_width}x{screen_height}")self.root.state('zoomed')  最大化窗口self.root.resizable(TrueTrue)绑定窗口大小变化事件,确保界面元素能适应窗口大小变化self.root.bind('<Configure>'self.on_window_resize)设置主题self.style = ttk.Style()self.style.theme_use('clam')变量self.folder_path = tk.StringVar()self.reference_image_path = tk.StringVar()self.processing = Falseself.processed_files = 0self.total_files = 0self.results = []self.reference_face_features = None创建UIself.create_ui()加载预训练的人脸检测器self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')def create_ui(self):主框架main_frame = ttk.Frame(self.root, padding="20")        main_frame.pack(fill=tk.BOTH, expand=True)移除标题,使用窗口标题即可文件夹和参考照片选择区域select_frame = ttk.LabelFrame(main_frame, text="设置"padding=15)        select_frame.pack(fill=tk.X, pady=10)左侧选择区域left_frame = ttk.Frame(select_frame)        left_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)文件夹选择folder_frame = ttk.Frame(left_frame)        folder_frame.pack(fill=tk.X, pady=5)        ttk.Label(folder_frame, text="选择文件夹:"font=("SimHei"12)).pack(side=tk.LEFT, padx=5anchor=tk.W)        ttk.Entry(folder_frame, textvariable=self.folder_path, width=30).pack(side=tk.LEFT, padx=5fill=tk.X,expand=True)        ttk.Button(folder_frame, text="浏览"command=self.browse_folder).pack(side=tk.LEFT, padx=5)参考照片选择ref_frame = ttk.Frame(left_frame)        ref_frame.pack(fill=tk.X, pady=5)        ttk.Label(ref_frame, text="参考照片:"font=("SimHei"12)).pack(side=tk.LEFT, padx=5anchor=tk.W)        ttk.Entry(ref_frame, textvariable=self.reference_image_path, width=30).pack(side=tk.LEFT, padx=5fill=tk.X,expand=True)        ttk.Button(ref_frame, text="选择"command=self.select_reference_image).pack(side=tk.LEFT, padx=5)控制按钮 - 放在参考照片输入框下面,参考照片预览的左边button_frame = ttk.Frame(left_frame)        button_frame.pack(fill=tk.X, pady=10)处理完整视频选项self.full_video_var = tk.BooleanVar(value=False)        full_video_check = ttk.Checkbutton(button_frame, text="处理完整视频"variable=self.full_video_var)        full_video_check.pack(side=tk.LEFT, padx=10)self.find_similar_button = ttk.Button(button_frame, text="查找相似人物"command=self.find_similar_faces, style="Accent.TButton")self.find_similar_button.pack(side=tk.LEFT, padx=10)self.stop_button = ttk.Button(button_frame, text="停止"command=self.stop_recognition, state=tk.DISABLED)self.stop_button.pack(side=tk.LEFT, padx=10)右侧参考照片预览right_frame = ttk.LabelFrame(select_frame, text="参考照片预览"padding=10width=250height=250)        right_frame.pack(side=tk.RIGHT, padx=10fill=tk.BOTH, expand=False)        right_frame.pack_propagate(False)  防止内容改变框架大小self.reference_preview_label = ttk.Label(right_frame)self.reference_preview_label.pack(fill=tk.BOTH, expand=Trueipadx=10ipady=10)进度条self.progress_var = tk.DoubleVar()        progress_frame = ttk.LabelFrame(main_frame, text="处理进度"padding=10)        progress_frame.pack(fill=tk.X, pady=10)        ttk.Label(progress_frame, text="进度:"font=("SimHei"12)).pack(side=tk.LEFT, padx=5anchor=tk.CENTER)self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)self.progress_bar.pack(side=tk.LEFT, padx=5fill=tk.X, expand=True)self.progress_label = ttk.Label(progress_frame, text="0/0 文件"font=("SimHei"10))self.progress_label.pack(side=tk.LEFT, padx=10anchor=tk.CENTER)结果和预览区域 - 占窗口的4分之3result_preview_frame = ttk.LabelFrame(main_frame, text="识别结果"padding=10)使用权重参数确保它占据大部分空间result_preview_frame.pack(fill=tk.BOTH, expand=Truepady=10)设置权重,让设置和处理进度只占界面的4分之一,识别结果占4分之3main_frame.rowconfigure(0weight=1)  设置区域权重为1main_frame.rowconfigure(1weight=1)  处理进度区域权重为1main_frame.rowconfigure(2weight=3)  结果预览区域权重为3        # 这样设置和处理进度的总权重为2,结果预览区域权重为3,总权重为5        # 但实际上,Tkinter会根据可见的行来分配空间,所以我们需要确保只有这三行为了更精确,我们可以重新组织布局,将设置和处理进度放在一个框架中然后为这个框架设置权重1,结果预览区域设置权重3        # 结果列表result_frame = ttk.LabelFrame(result_preview_frame, text="识别结果"padding=10)        result_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=Truepadx=(010))        columns = ("file""faces""similarity")self.tree = ttk.Treeview(result_frame, columns=columns, show="headings")设置表头和排序功能for col in columns:self.tree.heading(col, text=col if col == "file" else ("检测到的人脸数if col == "faces" else "相似度"), command=lambda _col=col: self.tree_sort_column(_col, False))self.tree.column("file"width=400)self.tree.column("faces"width=100anchor=tk.CENTER)self.tree.column("similarity"width=100anchor=tk.CENTER)绑定双击事件self.tree.bind("<Double-1>"self.on_double_click)绑定选择事件self.tree.bind("<<TreeviewSelect>>"self.on_tree_select)        scrollbar = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.tree.yview)self.tree.configure(yscroll=scrollbar.set)        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)self.tree.pack(fill=tk.BOTH, expand=True)预览区域(在列表右边)preview_frame = ttk.LabelFrame(result_preview_frame, text="预览"padding=10width=300)        preview_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=(100))        preview_frame.pack_propagate(False)self.preview_label = ttk.Label(preview_frame)self.preview_label.pack(fill=tk.BOTH, expand=Trueipadx=10ipady=10)def browse_folder(self):        folder = filedialog.askdirectory()if folder:self.folder_path.set(folder)def select_reference_image(self):        file_types = [('Image files''*.jpg *.jpeg *.png *.bmp *.gif'), ('All files''*.*')]        file_path = filedialog.askopenfilename(filetypes=file_types)if file_path:print(f"选择的参考照片路径{file_path}")print(f"文件是否存在{os.path.exists(file_path)}")self.reference_image_path.set(file_path)显示参考照片预览print("调用show_reference_preview")self.show_reference_preview(file_path)提取参考照片的人脸特征self.extract_reference_face_features()def extract_reference_face_features(self):"""提取参考照片的人脸特征"""image_path = self.reference_image_path.get()if not image_path:return        try:使用PIL读取图像,处理非ASCII路径from PIL import Imageimport numpy as np打开图像pil_img = Image.open(image_path)转换为numpy数组img = np.array(pil_img)转换为BGR格式(OpenCV使用的格式)if len(img.shape) == 3:                img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)if img is None:                messagebox.showerror("错误""无法读取参考照片")returngray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)调整人脸检测参数,使其更加宽松,提高检测率faces = self.face_cascade.detectMultiScale(                gray, scaleFactor=1.03,  更小的缩放因子,检测更精细minNeighbors=2,     更少的邻居,减少漏检minSize=(1515),   更小的最小人脸尺寸maxSize=(400400)  更大的最大人脸尺寸)如果没有检测到人脸,尝试使用不同的参数组合if len(faces) == 0:print("尝试使用更宽松的参数")                faces = self.face_cascade.detectMultiScale(                    gray, scaleFactor=1.02,  非常小的缩放因子minNeighbors=1,     最少的邻居minSize=(1010),   非常小的最小人脸尺寸maxSize=(500500)  更大的最大人脸尺寸)print(f"检测到的人脸数量{len(faces)}")if len(faces) == 0:                messagebox.showerror("错误""参考照片中未检测到人脸")self.reference_face_features = None                return只使用第一个检测到的人脸x, y, w, h = faces[0]            face_roi = gray[y:y + h, x:x + w]            face_roi = cv2.resize(face_roi, (100100))  调整为固定大小提取特征(使用简单的直方图作为特征)hist = cv2.calcHist([face_roi], [0], None, [256], [0256])            hist = cv2.normalize(hist, hist).flatten()self.reference_face_features = hist            messagebox.showinfo("成功""参考照片人脸特征提取成功")显示参考照片for (x, y, w, h) in faces:                cv2.rectangle(img, (x, y), (x + w, y + h), (02550), 2)self.update_preview(img)except Exception as e:print(f"提取参考人脸特征时出错{e}")            messagebox.showerror("错误"f"提取人脸特征时出错{str(e)}")self.reference_face_features = None    def load_image(self, image_path):"""读取图像,处理非ASCII路径"""try:使用PIL库读取图像,它对非ASCII路径支持更好from PIL import Imageimport numpy as np打开图像pil_img = Image.open(image_path)转换为numpy数组img = np.array(pil_img)转换为BGR格式(OpenCV使用的格式)if len(img.shape) == 3:                img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)return imgexcept Exception as e:print(f"读取图像时出错{e}")return None    def read_image(self, image_path):"""读取图像,处理非ASCII路径"""return self.load_image(image_path)def show_reference_preview(self, image_path):"""显示参考照片预览"""try:print(f"显示参考照片{image_path}")使用PIL读取图像,处理非ASCII路径from PIL import Imageimport numpy as np打开图像pil_img = Image.open(image_path)转换为numpy数组img = np.array(pil_img)转换为BGR格式(OpenCV使用的格式)if len(img.shape) == 3:                img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)print(f"读取图像返回{img is not None}")if img is not None:调整图像大小以适应预览区域h, w = img.shape[:2]print(f"图像尺寸{w}x{h}")                max_size = 180if h > max_size or w > max_size:if h > w:                        scale = max_size / helse:                        scale = max_size / w                    img = cv2.resize(img, (int(w * scale), int(h * scale)))print(f"调整后尺寸{int(w * scale)}x{int(h * scale)}")转换为RGB并显示img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)                img = Image.fromarray(img)                img = ImageTk.PhotoImage(img)print("创建PhotoImage成功")使用after确保在主线程中更新UIdef update_preview_label():print("更新preview_label")self.reference_preview_label.config(image=img)self.reference_preview_label.image = img  保持引用print("更新完成")self.root.after(0, update_preview_label)print("调用after完成")else:print("无法读取图像")def show_error():self.reference_preview_label.config(text="无法加载图片"justify=tk.CENTER)self.root.after(0, show_error)except Exception as e:print(f"显示参考照片预览时出错{e}")显示错误信息error_msg = f"无法加载图片{str(e)}"def show_error():self.reference_preview_label.config(text=error_msg, justify=tk.CENTER)self.root.after(0, show_error)def calculate_similarity(self, face_features):"""计算两个人脸特征之间的相似度"""if self.reference_face_features is None:return 0.0使用余弦相似度similarity = cv2.compareHist(self.reference_face_features, face_features, cv2.HISTCMP_CORREL)return similaritydef find_similar_faces(self):        folder = self.folder_path.get()if not folder:            messagebox.showerror("错误""请选择文件夹")return        if not os.path.exists(folder):            messagebox.showerror("错误""文件夹不存在")return        if not self.reference_image_path.get():            messagebox.showerror("错误""请选择参考照片")return        if self.reference_face_features is None:            messagebox.showerror("错误""无法从参考照片中提取人脸特征")return清空之前的结果for item in self.tree.get_children():self.tree.delete(item)self.results = []self.processed_files = 0self.total_files = 0self.progress_var.set(0)self.progress_label.config(text="0/0 文件")开始处理self.processing = Trueself.find_similar_button.config(state=tk.DISABLED)self.stop_button.config(state=tk.NORMAL)在新线程中处理文件thread = threading.Thread(target=self.process_folder, args=(folder,))        thread.daemon = Truethread.start()def stop_recognition(self):self.processing = Falseself.find_similar_button.config(state=tk.NORMAL)self.stop_button.config(state=tk.DISABLED)def process_folder(self, folder):获取所有图片和视频文件files = []for root, _, filenames in os.walk(folder):for filename in filenames:                ext = os.path.splitext(filename)[1].lower()if ext in ['.jpg''.jpeg''.png''.bmp''.gif''.mp4''.avi''.mov''.wmv']:                    files.append(os.path.join(root, filename))self.total_files = len(files)self.root.after(0lambdaself.progress_label.config(text=f"0/{self.total_files文件"))for i, file_path in enumerate(files):if not self.processing:break            try:                ext = os.path.splitext(file_path)[1].lower()if ext in ['.jpg''.jpeg''.png''.bmp''.gif']:                    faces, similarity = self.process_image_for_similarity(file_path)else:                    faces, similarity = self.process_video_for_similarity(file_path)self.results.append((file_path, faces, similarity))self.processed_files += 1更新UIprogress = (self.processed_files / self.total_files) * 100self.root.after(0lambda p=progress, f=file_path, fc=faces, s=similarity: self.update_ui(p, f, fc, s))except Exception as e:print(f"处理文件 {file_path时出错{e}")self.processed_files += 1progress = (self.processed_files / self.total_files) * 100self.root.after(0lambda p=progress, f=file_path: self.update_ui(p, f, "错误""-"))短暂休息,避免UI卡顿time.sleep(0.1)处理完成后按相似度排序并显示最相似的结果self.root.after(0self.sort_and_show_best_result)def on_window_resize(self, event):"""处理窗口大小变化事件"""这里可以添加调整界面元素大小的代码例如:调整预览区域的大小,调整表格的大小等pass    def process_image(self, image_path):使用PIL读取图像,处理非ASCII路径from PIL import Imageimport numpy as np打开图像pil_img = Image.open(image_path)转换为numpy数组img = np.array(pil_img)转换为BGR格式(OpenCV使用的格式)if len(img.shape) == 3:            img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)if img is None:return 0gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)使用与参考图像相同的宽松人脸检测参数faces = self.face_cascade.detectMultiScale(            gray,scaleFactor=1.03,  更小的缩放因子,检测更精细minNeighbors=2,     更少的邻居,减少漏检minSize=(1515),   更小的最小人脸尺寸maxSize=(400400)  更大的最大人脸尺寸)如果没有检测到人脸,尝试使用不同的参数组合if len(faces) == 0:print(f"文件 {image_path尝试使用更宽松的参数")            faces = self.face_cascade.detectMultiScale(                gray,scaleFactor=1.02,  非常小的缩放因子minNeighbors=1,     最少的邻居minSize=(1010),   非常小的最小人脸尺寸maxSize=(500500)  更大的最大人脸尺寸)绘制人脸框for (x, y, w, h) in faces:            cv2.rectangle(img, (x, y), (x + w, y + h), (02550), 2)更新预览self.update_preview(img)return len(faces)def process_image_for_similarity(self, image_path):使用PIL读取图像,处理非ASCII路径from PIL import Imageimport numpy as np打开图像pil_img = Image.open(image_path)转换为numpy数组img = np.array(pil_img)转换为BGR格式(OpenCV使用的格式)if len(img.shape) == 3:            img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)if img is None:return 0"-"gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)        faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1minNeighbors=5minSize=(3030))        max_similarity = 0.0绘制人脸框并计算相似度for (x, y, w, h) in faces:            cv2.rectangle(img, (x, y), (x + w, y + h), (02550), 2)提取人脸特征face_roi = gray[y:y + h, x:x + w]            face_roi = cv2.resize(face_roi, (100100))            hist = cv2.calcHist([face_roi], [0], None, [256], [0256])            hist = cv2.normalize(hist, hist).flatten()计算相似度similarity = self.calculate_similarity(hist)            max_similarity = max(max_similarity, similarity)更新预览self.update_preview(img)return len(faces), f"{max_similarity:.2f}"def process_video(self, video_path):        cap = cv2.VideoCapture(video_path)        total_faces = 0frame_count = 0确定是否处理完整视频process_full_video = getattr(self'full_video_var', tk.BooleanVar(value=False)).get()while cap.isOpened() and (process_full_video or frame_count < 10):            ret, frame = cap.read()if not ret:breakgray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)            faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1minNeighbors=5minSize=(3030))            total_faces += len(faces)绘制人脸框并更新预览(仅第一帧)if frame_count == 0:for (x, y, w, h) in faces:                    cv2.rectangle(frame, (x, y), (x+w, y+h), (02550), 2)self.update_preview(frame)            frame_count += 1检查是否需要停止处理if not getattr(self'processing'True):breakcap.release()return total_facesdef process_video_for_similarity(self, video_path):        cap = cv2.VideoCapture(video_path)        total_faces = 0max_similarity = 0.0frame_count = 0video_faces = []  存储视频中的人脸信息确定是否处理完整视频process_full_video = getattr(self'full_video_var', tk.BooleanVar(value=False)).get()创建视频预览窗口preview_window = None        if process_full_video:            preview_window = tk.Toplevel(self.root)            preview_window.title(f"视频处理预览{os.path.basename(video_path)}")            preview_window.geometry("600x400")创建滚动框架main_frame = ttk.Frame(preview_window, padding=10)            main_frame.pack(fill=tk.BOTH, expand=True)            canvas = tk.Canvas(main_frame)            scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=canvas.yview)            scrollable_frame = ttk.Frame(canvas)            scrollable_frame.bind("<Configure>",lambda e: canvas.configure(scrollregion=canvas.bbox("all")                )            )            canvas.create_window((00), window=scrollable_frame, anchor="nw")            canvas.configure(yscrollcommand=scrollbar.set)            canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)            scrollbar.pack(side=tk.RIGHT, fill=tk.Y)添加标题ttk.Label(scrollable_frame, text="正在处理的视频截图"font=('SimHei'12'bold')).pack(pady=5)处理视频帧while cap.isOpened() and (process_full_video or frame_count < 10):            ret, frame = cap.read()if not ret:break获取当前帧的时间current_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0  转换为秒gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)            faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1minNeighbors=5minSize=(3030))            total_faces += len(faces)计算相似度for (x, y, w, h) in faces:                face_roi = gray[y:y+h, x:x+w]                face_roi = cv2.resize(face_roi, (100100))                hist = cv2.calcHist([face_roi], [0], None, [256], [0256])                hist = cv2.normalize(hist, hist).flatten()                similarity = self.calculate_similarity(hist)                max_similarity = max(max_similarity, similarity)保存人脸信息video_faces.append({'time': current_time,'coords': (x, y, w, h),'similarity': similarity,'frame': frame.copy()                })绘制人脸框并更新预览for (x, y, w, h) in faces:                cv2.rectangle(frame, (x, y), (x+w, y+h), (02550), 2)第一帧更新主预览if frame_count == 0:self.update_preview(frame)如果处理完整视频,更新预览窗口if process_full_video and preview_window and frame_count % 10 == 0:  10帧更新一次调整大小h_frame, w_frame = frame.shape[:2]                max_size = 400if h_frame > max_size or w_frame > max_size:if h_frame > w_frame:                        scale = max_size / h_frameelse:                        scale = max_size / w_frame                    frame_resized = cv2.resize(frame, (int(w_frame*scale), int(h_frame*scale)))else:                    frame_resized = frame.copy()转换为RGB并显示frame_resized = cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)                frame_resized = Image.fromarray(frame_resized)                frame_resized = ImageTk.PhotoImage(frame_resized)创建帧信息frame_info = ttk.LabelFrame(scrollable_frame, text=f" #{frame_count} - {current_time:.2f}"padding=5)                frame_info.pack(fill=tk.X, pady=5)                frame_label = ttk.Label(frame_info, image=frame_resized)                frame_label.image = frame_resized  保持引用frame_label.pack()自动滚动到底部canvas.yview_moveto(1.0)            frame_count += 1检查是否需要停止处理if not getattr(self'processing'True):breakcap.release()关闭预览窗口if preview_window:            preview_window.destroy()保存视频人脸信息到结果中if not hasattr(self'video_faces_info'):self.video_faces_info = {}self.video_faces_info[video_path] = video_facesreturn total_faces, f"{max_similarity:.2f}"def update_preview(self, img):转换为RGBimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)转换为PIL图像img = Image.fromarray(img)获取预览标签的实际大小def resize_and_display():获取预览标签的宽度和高度label_width = self.preview_label.winfo_width()            label_height = self.preview_label.winfo_height()确保标签有足够的大小if label_width > 10 and label_height > 10:获取原始图像大小img_width, img_height = img.size计算缩放比例,保持原始比例scale = min(label_width / img_width, label_height / img_height, 1.0)计算新的图像大小new_width = int(img_width * scale)                new_height = int(img_height * scale)调整图像大小resized_img = img.resize((new_width, new_height), Image.LANCZOS)转换为PhotoImagephoto_img = ImageTk.PhotoImage(resized_img)更新标签self.preview_label.config(image=photo_img)self.preview_label.image = photo_img  保持引用使用after确保在主线程中更新UI,并且标签大小已经计算self.root.after(100, resize_and_display)def update_ui(self, progress, file_path, faces, similarity="-"):self.progress_var.set(progress)self.progress_label.config(text=f"{self.processed_files}/{self.total_files文件")添加到结果列表self.tree.insert("", tk.END, values=(file_path, faces, similarity))def sort_and_show_best_result(self):"""按相似度排序并显示最相似的结果"""按相似度排序结果def get_similarity_value(item):try:return float(item[2]) if item[2] != "-" and item[2] != "错误else -1except ValueError:return -1self.results.sort(key=get_similarity_value, reverse=True)清空当前列表并重新插入排序后的结果for item in self.tree.get_children():self.tree.delete(item)for result in self.results:self.tree.insert("", tk.END, values=result)显示最相似的结果if self.results:            best_result = self.results[0]            best_file_path = best_result[0]显示最相似的图片或视频帧ext = os.path.splitext(best_file_path)[1].lower()if ext in ['.jpg''.jpeg''.png''.bmp''.gif']:使用PIL读取图像,处理非ASCII路径from PIL import Imageimport numpy as np打开图像pil_img = Image.open(best_file_path)转换为numpy数组img = np.array(pil_img)转换为BGR格式(OpenCV使用的格式)if len(img.shape) == 3:                    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)if img is not None:检测人脸并绘制框gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)                    faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1minNeighbors=5minSize=(3030))for (x, y, w, h) in faces:                        cv2.rectangle(img, (x, y), (x+w, y+h), (02550), 2)self.update_preview(img)else:处理视频,显示第一帧cap = cv2.VideoCapture(best_file_path)                ret, frame = cap.read()if ret:                    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)                    faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1minNeighbors=5minSize=(3030))for (x, y, w, h) in faces:                        cv2.rectangle(frame, (x, y), (x+w, y+h), (02550), 2)self.update_preview(frame)                cap.release()完成处理self.finish_processing()def finish_processing(self):self.processing = Falseself.find_similar_button.config(state=tk.NORMAL)self.stop_button.config(state=tk.DISABLED)        messagebox.showinfo("完成"f"识别完成!共处理 {self.processed_files个文件")def tree_sort_column(self, col, reverse):"""排序树状列表"""获取所有项目items = [(self.tree.set(k, col), k) for in self.tree.get_children('')]尝试将值转换为数字进行排序try:            items.sort(key=lambda x: float(x[0]) if x[0] != "-" and x[0] != "错误else -1reverse=reverse)except ValueError:如果转换失败,按字符串排序items.sort(key=lambda x: x[0], reverse=reverse)重新排列项目for index, (val, k) in enumerate(items):self.tree.move(k, '', index)切换排序方向self.tree.heading(col, command=lambda _col=col: self.tree_sort_column(_col, not reverse))def on_double_click(selfevent):"""双击打开文件"""item = self.tree.selection()[0]        file_path = self.tree.item(item, "values")[0]if os.path.exists(file_path):try:                os.startfile(file_path)except Exception as e:                messagebox.showerror("错误"f"无法打开文件{str(e)}")else:            messagebox.showerror("错误""文件不存在")def on_tree_select(selfevent):"""当选中树状列表项时"""selected_items = self.tree.selection()if selected_items:            item = selected_items[0]            file_path = self.tree.item(item, "values")[0]检查是否是视频文件ext = os.path.splitext(file_path)[1].lower()if ext in ['.mp4''.avi''.mov''.wmv']:显示视频人脸信息self.show_video_face_info(file_path)else:对于图片,显示图片预览使用PIL读取图像,处理非ASCII路径from PIL import Imageimport numpy as np打开图像pil_img = Image.open(file_path)转换为numpy数组img = np.array(pil_img)转换为BGR格式(OpenCV使用的格式)if len(img.shape) == 3:                    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)if img is not None:                    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)                    faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1minNeighbors=5minSize=(3030))for (x, y, w, h) in faces:                        cv2.rectangle(img, (x, y), (x+w, y+h), (02550), 2)self.update_preview(img)def show_video_face_info(self, video_path):"""显示视频人脸信息"""if hasattr(self'video_faces_info'and video_path in self.video_faces_info:            video_faces = self.video_faces_info[video_path]if video_faces:创建视频人脸信息窗口face_info_window = tk.Toplevel(self.root)                face_info_window.title(f"视频人脸信息{os.path.basename(video_path)}")                face_info_window.geometry("900x600")主框架main_frame = ttk.Frame(face_info_window, padding=10)                main_frame.pack(fill=tk.BOTH, expand=True)表格框架table_frame = ttk.LabelFrame(main_frame, text="人脸信息列表"padding=10)                table_frame.pack(fill=tk.BOTH, expand=Truepady=5)创建表格columns = ("index""time""similarity")                tree = ttk.Treeview(table_frame, columns=columns, show="headings")设置表头tree.heading("index"text="序号")                tree.heading("time"text="时间()"command=lambdaself.sort_tree(tree, "time"False))                tree.heading("similarity"text="相似度"command=lambdaself.sort_tree(tree, "similarity"False))设置列宽tree.column("index"width=50anchor=tk.CENTER)                tree.column("time"width=100anchor=tk.CENTER)                tree.column("similarity"width=100anchor=tk.CENTER)添加滚动条scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=tree.yview)                tree.configure(yscroll=scrollbar.set)                scrollbar.pack(side=tk.RIGHT, fill=tk.Y)                tree.pack(fill=tk.BOTH, expand=True)预览框架preview_frame = ttk.LabelFrame(main_frame, text="视频截图"padding=10)                preview_frame.pack(fill=tk.BOTH, expand=Truepady=5)                preview_label = ttk.Label(preview_frame)                preview_label.pack(fill=tk.BOTH, expand=True)填充表格数据for i, face_info in enumerate(video_faces):                    tree.insert("", tk.END, values=(i+1f"{face_info['time']:.2f}"f"{face_info['similarity']:.2f}"), tags=(i,))定义更新预览的函数def update_preview_with_face(face_info):显示该时间段的视频截图frame = face_info['frame']                    x, y, w, h = face_info['coords']绘制人脸框cv2.rectangle(frame, (x, y), (x+w, y+h), (02550), 2)保存原始帧以便调整大小face_info['original_frame'] = frame.copy()调整大小以适应当前窗口def resize_preview():if 'original_frame' not in face_info:returnframe = face_info['original_frame'].copy()检查帧是否有效if frame is None or frame.size == 0:return获取预览标签的大小width = preview_label.winfo_width()                        height = preview_label.winfo_height()if width > 10 and height > 10:  确保窗口已经初始化h_frame, w_frame = frame.shape[:2]计算缩放比例scale = min(width / w_frame, height / h_frame, 1.0)  不放大超过原始大小if scale < 1.0:                                frame = cv2.resize(frame, (int(w_frame*scale), int(h_frame*scale)))转换为RGB并显示frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)                            frame = Image.fromarray(frame)                            frame = ImageTk.PhotoImage(frame)                            preview_label.config(image=frame)                            preview_label.image = frame  保持引用初始调整大小,使用after确保窗口已经完全初始化def delayed_resize():                        resize_preview()                    face_info_window.after(100, delayed_resize)绑定窗口大小变化事件def on_configure(event):                        resize_preview()                    preview_frame.bind("<Configure>", on_configure)双击事件处理def on_tree_double_click(event):                    item = tree.selection()[0]                    index = int(tree.item(item, "tags")[0])                    face_info = video_faces[index]                    update_preview_with_face(face_info)                tree.bind("<Double-1>", on_tree_double_click)绑定选择事件,显示选中的人脸def on_tree_select(event):if tree.selection():                        item = tree.selection()[0]                        index = int(tree.item(item, "tags")[0])                        face_info = video_faces[index]                        update_preview_with_face(face_info)                tree.bind("<<TreeviewSelect>>", on_tree_select)默认选中第一个项目并显示预览if tree.get_children():                    tree.selection_set(tree.get_children()[0])手动触发选择事件,显示第一张人脸first_item = tree.get_children()[0]                    first_index = int(tree.item(first_item, "tags")[0])                    first_face_info = video_faces[first_index]                    update_preview_with_face(first_face_info)else:                messagebox.showinfo("信息""该视频中未检测到人脸")else:            messagebox.showinfo("信息""未找到视频人脸信息")def sort_tree(self, tree, col, reverse):"""排序表格"""获取所有项目items = [(tree.set(k, col), k) for in tree.get_children('')]尝试将值转换为数字进行排序try:            items.sort(key=lambda x: float(x[0]), reverse=reverse)except ValueError:如果转换失败,按字符串排序items.sort(key=lambda x: x[0], reverse=reverse)重新排列项目for index, (val, k) in enumerate(items):            tree.move(k, '', index)切换排序方向if col == "time":            tree.heading(col, command=lambdaself.sort_tree(tree, col, not reverse))elif col == "similarity":            tree.heading(col, command=lambdaself.sort_tree(tree, col, not reverse))if __name__ == "__main__":    root = tk.Tk()    app = AIRecognitionApp(root)    root.mainloop()