662. Maximum Width of Binary Tree

本文介绍了一种求解二叉树最大宽度的算法实现,通过遍历每一层的节点来确定最大宽度,并提供了具体的代码示例。适用于二叉树结构的宽度计算问题。

Given a binary tree, write a function to get the maximum width of the given tree. The width of a tree is the maximum width among all levels. The binary tree has the same structure as a full binary tree, but some nodes are null.

The width of one level is defined as the length between the end-nodes (the leftmost and right most non-null nodes in the level, where the null nodes between the end-nodes are also counted into the length calculation.

 

Example 1:

Input: 

           1
         /   \
        3     2
       / \     \  
      5   3     9 

Output: 4
Explanation: The maximum width existing in the third level with the length 4 (5,3,null,9).

Example 2:

Input: 

          1
         /  
        3    
       / \       
      5   3     

Output: 2
Explanation: The maximum width existing in the third level with the length 2 (5,3).

Example 3:

Input: 

          1
         / \
        3   2 
       /        
      5      

Output: 2
Explanation: The maximum width existing in the second level with the length 2 (3,2).

Example 4:

Input: 

          1
         / \
        3   2
       /     \  
      5       9 
     /         \
    6           7
Output: 8
Explanation:The maximum width existing in the fourth level with the length 8 (6,null,null,null,null,null,null,7).


Note: Answer will in the range of 32-bit signed integer.

 

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int widthOfBinaryTree(TreeNode* root) {
        if(root == NULL)
            return 0;
        queue<TreeNode *> real_queue;
        queue<int> virtual_queue;
        
        //利用实队列和虚队列 实队列保存节点信息 虚队列保存节点位置信息 LeetCode中的discuss利用递归方法 更是巧妙
        int lastLayerSum = 1;
        int currentLayerSum = 0;
        int MaxSum = 0;
        bool first = true;         // 是否为每层的第一个节点
        int firstflag = 0;         // 表示每层第一个节点的编号
        
        real_queue.push(root);
        virtual_queue.push(1);
        
        while(!real_queue.empty())
        {
            currentLayerSum = 0;
            first = true;
            while(lastLayerSum)
            {
                TreeNode * node = real_queue.front();
                real_queue.pop();
                int flag = virtual_queue.front();
                virtual_queue.pop();
                
                if(first)
                {
                    firstflag = flag;
                    first = false;
                }
                if ((flag-firstflag+1)>MaxSum)
                    MaxSum = flag-firstflag+1;
               
                if(node->left) 
                {
                    real_queue.push(node->left);
                    virtual_queue.push(2*flag);
                    currentLayerSum ++ ;
                }
                
                if(node->right) 
                {
                    real_queue.push(node->right);
                    virtual_queue.push(2*flag+1);
                    currentLayerSum ++;
                }
                lastLayerSum--;
            }
            lastLayerSum = currentLayerSum;
        }
        
        return MaxSum;
    }
};

 

 

leetcode例子:

 int widthOfBinaryTree(TreeNode* root) {
        return dfs(root, 0, 1, vector<pair<int, int>>() = {});
    }
    
    int dfs(TreeNode* root, int level, int order, vector<pair<int, int>>& vec){
        if(root == NULL)return 0;
        if(vec.size() == level)vec.push_back({order, order});
        else vec[level].second = order;
        return max({vec[level].second - vec[level].first + 1, dfs(root->left, level + 1, 2*order, vec), dfs(root->right, level + 1, 2*order + 1, vec)});
    }

 

 

 

这是我的日志:[Running] python -u "e:\system\Desktop\项目所需文件\工具\文件搜索工具\File Content Search Tool.py" libpng warning: iCCP: known incorrect sRGB profile libpng warning: iCCP: known incorrect sRGB profile Exception in thread Thread-1 (perform_search): Traceback (most recent call last): File "e:\system\Desktop\\u9879�ڏ�������\�H��\�����r���H��\File Content Search Tool.py", line 437, in search_in_office_file wb = load_workbook(filepath, read_only=True, data_only=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\site-packages\openpyxl\reader\excel.py", line 346, in load_workbook reader = ExcelReader(filename, read_only, keep_vba, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\site-packages\openpyxl\reader\excel.py", line 123, in __init__ self.archive = _validate_archive(fn) ^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\site-packages\openpyxl\reader\excel.py", line 95, in _validate_archive archive = ZipFile(filename, 'r') ^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\zipfile\__init__.py", line 1331, in __init__ self.fp = io.open(file, filemode) ^^^^^^^^^^^^^^^^^^^^^^^ PermissionError: [Errno 13] Permission denied: 'E:/SVN/DH_D82D_D83D/trunk/10COMMUNICATION/1001Minutes/12.DRBFM\\~$D82DD83D_CANDI�Ή�_DRBFM.xlsm' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "e:\system\Desktop\\u9879�ڏ�������\�H��\�����r���H��\File Content Search Tool.py", line 330, in perform_search matches = self.search_in_office_file(file_path, pattern) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "e:\system\Desktop\\u9879�ڏ�������\�H��\�����r���H��\File Content Search Tool.py", line 534, in search_in_office_file print(f"\u5904��Office������\u8d25 {filepath}: {str(e)}") UnicodeEncodeError: 'cp932' codec can't encode character '\u5904' in position 0: illegal multibyte sequence During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1075, in _bootstrap_inner self.run() File "C:\Users\cheny9210\AppData\Local\Programs\Python\Python312\Lib\threading.py", line 1012, in run self._target(*self._args, **self._kwargs) File "e:\system\Desktop\\u9879�ڏ�������\�H��\�����r���H��\File Content Search Tool.py", line 359, in perform_search print(error_info) UnicodeEncodeError: 'cp932' codec can't encode character '\u9519' in position 2: illegal multibyte sequence 这是我的代码:import os import re import sys import time import threading import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter.font import Font import fnmatch import subprocess import shutil import docx from openpyxl import load_workbook import PyPDF2 import zipfile import chardet import xlrd # 添加对旧版Excel的支持 class FileSearchApp: def __init__(self, master): self.master = master master.title("高级文件搜索工具") master.geometry("1200x800") master.minsize(900, 650) # 设置现代主题 self.style = ttk.Style() self.style.theme_use("vista" if sys.platform == "win32" else "aqua") # 创建主框架 main_frame = ttk.Frame(master, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 创建左侧搜索面板 search_frame = ttk.LabelFrame(main_frame, text="搜索选项", padding=10) search_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10), pady=5) # 使用网格布局管理器 row = 0 ttk.Label(search_frame, text="搜索目录:").grid(row=row, column=0, sticky="w", pady=5) self.dir_entry = ttk.Entry(search_frame, width=40) self.dir_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") self.dir_entry.insert(0, os.getcwd()) ttk.Button(search_frame, text="浏览...", command=self.browse_directory).grid(row=row, column=2, padx=5, pady=5) row += 1 ttk.Label(search_frame, text="关键词:").grid(row=row, column=0, sticky="w", pady=5) self.keyword_entry = ttk.Entry(search_frame, width=40) self.keyword_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") row += 1 ttk.Label(search_frame, text="文件过滤:").grid(row=row, column=0, sticky="w", pady=5) self.filter_entry = ttk.Entry(search_frame, width=40) self.filter_entry.grid(row=row, column=1, padx=5, pady=5, sticky="we") self.filter_entry.insert(0, "*") row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 搜索选项 options_frame = ttk.Frame(search_frame) options_frame.grid(row=row, column=0, columnspan=3, sticky="we", padx=5, pady=5) # 使用网格布局替代pack布局,更紧凑 self.case_var = tk.BooleanVar(value=False) ttk.Checkbutton(options_frame, text="忽略大小写", variable=self.case_var).grid(row=0, column=0, sticky="w", padx=(0, 10)) self.regex_var = tk.BooleanVar(value=False) ttk.Checkbutton(options_frame, text="正则表达式", variable=self.regex_var).grid(row=0, column=1, sticky="w", padx=(0, 10)) self.binary_var = tk.BooleanVar(value=False) self.binary_check = ttk.Checkbutton(options_frame, text="包含二进制", variable=self.binary_var) self.binary_check.grid(row=0, column=2, sticky="w") # 添加文件大小限制选项 self.limit_var = tk.BooleanVar(value=True) ttk.Checkbutton(options_frame, text="限制大小(100MB)", variable=self.limit_var).grid(row=0, column=3, sticky="w", padx=(10, 0)) row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 搜索按钮 button_frame = ttk.Frame(search_frame) button_frame.grid(row=row, column=0, columnspan=3, pady=10) self.search_button = ttk.Button(button_frame, text="开始搜索", command=self.start_search) self.search_button.pack(side=tk.LEFT, padx=5) self.stop_button = ttk.Button(button_frame, text="停止搜索", command=self.stop_search, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=5) self.export_button = ttk.Button(button_frame, text="导出结果", command=self.export_results) self.export_button.pack(side=tk.LEFT, padx=5) row += 1 # 添加分隔线 ttk.Separator(search_frame, orient=tk.HORIZONTAL).grid(row=row, column=0, columnspan=3, sticky="ew", pady=10) row += 1 # 状态栏 status_frame = ttk.Frame(search_frame) status_frame.grid(row=row, column=0, columnspan=3, sticky="we", pady=5) # 状态标签(左对齐) self.status_label = ttk.Label(status_frame, text="就绪", font=("Arial", 9)) self.status_label.pack(side=tk.LEFT, anchor='w') # 进度条(中间,可伸缩) self.progress_var = tk.DoubleVar() self.progress_bar = ttk.Progressbar( status_frame, variable=self.progress_var, length=200, mode='determinate' ) self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) # 结果统计(右对齐) self.stats_label = ttk.Label(status_frame, text="", font=("Arial", 9)) self.stats_label.pack(side=tk.RIGHT) # 创建结果面板 results_frame = ttk.LabelFrame(main_frame, text="搜索结果", padding=10) results_frame.pack(fill=tk.BOTH, expand=True, padx=(5, 0), pady=5) # 分割窗格 paned_window = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) paned_window.pack(fill=tk.BOTH, expand=True) # 左侧文件列表 file_list_frame = ttk.Frame(paned_window) paned_window.add(file_list_frame, weight=1) # 使用Treeview替代Listbox columns = ("filename", "path") self.file_tree = ttk.Treeview(file_list_frame, columns=columns, show="headings", selectmode="browse") # 设置列标题 self.file_tree.heading("filename", text="文件名") self.file_tree.heading("path", text="路径") # 设置列宽 self.file_tree.column("filename", width=200, anchor="w") self.file_tree.column("path", width=300, anchor="w") self.file_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.file_tree.bind('<<TreeviewSelect>>', self.show_file_content) self.file_tree.bind('<Double-1>', self.open_selected_file) file_scroll = ttk.Scrollbar(file_list_frame, command=self.file_tree.yview) file_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.file_tree.config(yscrollcommand=file_scroll.set) # 右键菜单 self.file_menu = tk.Menu(self.master, tearoff=0) self.file_menu.add_command(label="打开文件", command=self.open_selected_file) self.file_menu.add_command(label="打开文件位置", command=self.open_file_location) self.file_tree.bind("<Button-3>", self.show_file_context_menu) # 右侧文件内容预览 content_frame = ttk.Frame(paned_window) paned_window.add(content_frame, weight=2) self.content_text = scrolledtext.ScrolledText( content_frame, wrap=tk.WORD, font=("Consolas", 10), padx=5, pady=5 ) self.content_text.pack(fill=tk.BOTH, expand=True) # 文本区域右键菜单 text_menu = tk.Menu(self.master, tearoff=0) text_menu.add_command(label="复制", command=self.copy_selected_text) self.content_text.bind("<Button-3>", lambda e: text_menu.tk_popup(e.x_root, e.y_root)) # 高亮标签 self.content_text.tag_configure("match", background="yellow") self.content_text.tag_configure("linenum", foreground="blue") self.content_text.tag_configure("header", foreground="darkgreen", font=("Arial", 10, "bold")) self.content_text.tag_configure("warning", foreground="red", font=("Arial", 10, "italic")) # 初始化变量 self.results = {} self.all_files = [] self.file_paths = [] self.stop_requested = False self.search_thread = None def browse_directory(self): directory = filedialog.askdirectory(title="选择搜索目录") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) def start_search(self): # 重置状态 self.progress_var.set(0) self.stop_requested = False self.results = {} self.all_files = [] self.file_paths = [] self.file_tree.delete(*self.file_tree.get_children()) self.content_text.delete(1.0, tk.END) self.status_label.config(text="正在搜索...") self.search_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.stats_label.config(text="") # 获取搜索参数 directory = self.dir_entry.get().strip() keyword = self.keyword_entry.get().strip() file_filter = self.filter_entry.get().strip() # 验证输入 if not directory or not os.path.isdir(directory): messagebox.showerror("错误", "请选择有效的搜索目录") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return if not keyword: messagebox.showerror("错误", "请输入搜索关键词") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return # 解析文件过滤器 if file_filter == "": filter_patterns = ['*'] else: separators = [';', '|', ' ', ','] for sep in separators: if sep in file_filter: filter_patterns = [pat.strip() for pat in file_filter.split(sep)] break else: filter_patterns = [file_filter] # 编译搜索模式 flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): pattern = re.compile(keyword, flags) else: escaped_keyword = re.escape(keyword) pattern = re.compile(escaped_keyword, flags) except re.error as e: messagebox.showerror("正则表达式错误", f"无效的正则表达式: {str(e)}") self.search_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) return # 在后台线程中执行搜索 self.search_thread = threading.Thread( target=self.perform_search, args=(directory, filter_patterns, pattern), daemon=True ) self.search_thread.start() def perform_search(self, directory, filter_patterns, pattern): """在后台线程中执行文件搜索""" try: # 收集所有匹配的文件 all_files = [] for root, _, files in os.walk(directory): if self.stop_requested: self.master.after(0, lambda: self.status_label.config(text="搜索已取消")) return for file in files: file_path = os.path.join(root, file) # 检查文件大小限制(避免处理超大文件) try: file_size = os.path.getsize(file_path) if file_size > 100 * 1024 * 1024: # 100MB continue except: continue # 检查是否符合任一过滤模式 if any(fnmatch.fnmatch(file, pat) for pat in filter_patterns): all_files.append(file_path) self.all_files = all_files total_files = len(all_files) # 初始化进度条 self.master.after(0, lambda: self.progress_bar.config(maximum=total_files)) self.master.after(0, lambda: self.stats_label.config(text=f"扫描到 {total_files} 个文件")) # 搜索每个文件 self.results = {} processed = 0 matches_found = 0 for file_path in self.all_files: if self.stop_requested: break processed += 1 # 更新进度条(安全方式) self.master.after(0, lambda v=processed: self.progress_var.set(v)) if processed % 10 == 0: # 每处理10个文件更新一次进度 self.master.after(0, lambda p=processed, t=total_files: self.stats_label.config(text=f"处理中: {p}/{t} 文件 ({round(p/t*100,1)}%)")) # 忽略二进制文件(除非用户选择包含) if not self.binary_var.get() and self.is_binary(file_path): continue # 获取文件扩展名 _, ext = os.path.splitext(file_path) ext_lower = ext.lower() # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.search_in_office_file(file_path, pattern) # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.search_in_archive(file_path, pattern) # 处理文本文件 else: matches = self.search_in_text_file(file_path, pattern) if matches: self.results[file_path] = matches matches_found += len(matches) # 在UI线程中添加文件到列表 filename = os.path.basename(file_path) self.master.after(0, lambda fp=file_path, fn=filename: self.file_tree.insert("", "end", values=(fn, fp))) # 更新完成状态 if self.stop_requested: status_text = f"搜索已取消 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" else: status_text = f"搜索完成 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" self.master.after(0, lambda: self.status_label.config(text=status_text)) self.master.after(0, lambda: self.stats_label.config(text=f"已处理 {processed}/{total_files} 文件")) self.master.after(0, lambda: self.progress_var.set(total_files)) except Exception as e: # 记录详细错误日志 error_info = f"搜索错误: {type(e).__name__} - {str(e)}" print(error_info) with open("search_errors.log", "a") as log: log.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_info}\n") import traceback traceback.print_exc(file=log) self.master.after(0, lambda: messagebox.showerror( "搜索错误", f"发生严重错误: {error_info}\n详细信息已记录到日志" )) finally: self.master.after(0, lambda: self.search_button.config(state=tk.NORMAL)) self.master.after(0, lambda: self.stop_button.config(state=tk.DISABLED)) self.search_thread = None def search_in_text_file(self, filepath, pattern): """在文本文件中搜索匹配项""" matches = [] try: encoding = self.detect_encoding(filepath) try: with open(filepath, 'r', encoding=encoding, errors='replace') as f: for line_num, line in enumerate(f, 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except UnicodeDecodeError: # 特殊编码处理回退 with open(filepath, 'rb') as f: content = f.read() try: text = content.decode('utf-8', errors='replace') except: text = content.decode('latin-1', errors='replace') for line_num, line in enumerate(text.splitlines(), 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"读取文本文件失败 {filepath}: {str(e)}") return matches def search_in_office_file(self, filepath, pattern): """在Office文件中搜索文本内容""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # DOCX文件处理 if ext_lower == '.docx': doc = docx.Document(filepath) # 搜索段落 for i, para in enumerate(doc.paragraphs, 1): if para.text and pattern.search(para.text): matches.append((i, f"段落 {i}: {para.text[:100]}" + ("..." if len(para.text) > 100 else ""))) # 搜索表格 for table in doc.tables: for row_idx, row in enumerate(table.rows, 1): for cell_idx, cell in enumerate(row.cells, 1): if cell.text and pattern.search(cell.text): content = cell.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((row_idx, f"表格 行{row_idx}列{cell_idx}: {content}")) # XLSX/XLS文件处理 elif ext_lower in ('.xlsx', '.xls', '.xlsm'): # 处理新格式Excel文件 if ext_lower in ('.xlsx', '.xlsm'): wb = load_workbook(filepath, read_only=True, data_only=True) for sheet_name in wb.sheetnames: sheet = wb[sheet_name] for row_idx, row in enumerate(sheet.iter_rows(values_only=True), 1): for col_idx, cell in enumerate(row, 1): if cell is not None and pattern.search(str(cell)): cell_ref = f"{chr(64+col_idx)}{row_idx}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx, f"工作表 '{sheet_name}' 单元格 {cell_ref}: {cell_value}")) # 处理旧格式Excel文件 elif ext_lower == '.xls': wb = xlrd.open_workbook(filepath) for sheet_idx in range(wb.nsheets): sheet = wb.sheet_by_index(sheet_idx) for row_idx in range(sheet.nrows): for col_idx in range(sheet.ncols): cell = sheet.cell_value(row_idx, col_idx) if cell and pattern.search(str(cell)): cell_ref = f"{chr(65+col_idx)}{row_idx+1}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx+1, f"工作表 '{sheet.name}' 单元格 {cell_ref}: {cell_value}")) # PPTX文件处理 elif ext_lower == '.pptx': from pptx import Presentation ppt = Presentation(filepath) # 搜索幻灯片文本 for slide_idx, slide in enumerate(ppt.slides, 1): for shape in slide.shapes: if hasattr(shape, "text"): if shape.text and pattern.search(shape.text): content = shape.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((slide_idx, f"幻灯片 {slide_idx}: {content}")) # PDF文件处理 elif ext_lower == '.pdf': with open(filepath, 'rb') as f: pdf = PyPDF2.PdfReader(f) for page_num in range(len(pdf.pages)): page_text = pdf.pages[page_num].extract_text() if page_text and pattern.search(page_text): # 提取匹配内容 matches_found = [] for match in pattern.finditer(page_text): context = page_text[max(0, match.start()-20):match.end()+20] context = context.replace('\n', ' ').strip() matches_found.append(context) # 添加到结果 if matches_found: preview = "; ".join(matches_found[:3]) # 显示前3个匹配 if len(matches_found) > 3: preview += f" ... (+{len(matches_found)-3} 更多)" matches.append((page_num+1, f"页面 {page_num+1}: {preview}")) # 旧版DOC文件处理 elif ext_lower == '.doc': try: # 尝试使用antiword转换DOC为文本 result = subprocess.run(['antiword', filepath], capture_output=True, text=True, timeout=10) if result.returncode == 0: doc_text = result.stdout for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception: # 备用方法:使用python-doc处理 import win32com.client word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(filepath) doc_text = doc.Content.Text doc.Close() word.Quit() for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"处理Office文件失败 {filepath}: {str(e)}") return matches def search_in_archive(self, filepath, pattern): """在压缩文件中搜索匹配项""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # ZIP文件处理 if ext_lower in ('.zip', '.jar', '.war'): with zipfile.ZipFile(filepath, 'r') as archive: for name in archive.namelist(): # 只处理文本文件和Office文档 if not name.endswith(('/')) and not self.is_binary(name): try: with archive.open(name) as file: content = file.read(4096) # 只读取前4KB # 尝试检测编码 result = chardet.detect(content) encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8' # 解码内容并搜索 try: text_content = content.decode(encoding, errors='replace') if pattern.search(text_content): matches.append((name, f"压缩文件中的文件: {name}")) except: # 二进制内容搜索 if pattern.search(content): matches.append((name, f"压缩文件中的文件(二进制内容): {name}")) except Exception: continue # 其他压缩格式(需要外部工具) elif ext_lower in ('.rar', '.7z', '.tar', '.gz'): # 使用7zip命令行工具解压并搜索 temp_dir = tempfile.mkdtemp() try: subprocess.run(['7z', 'x', filepath, f'-o{temp_dir}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=60) # 递归搜索解压的目录 for root, _, files in os.walk(temp_dir): for file in files: full_path = os.path.join(root, file) _, file_ext = os.path.splitext(file) file_ext = file_ext.lower() # 只在文本/Office文件中搜索 if file_ext in ['', '.txt', '.py', '.java', '.c', '.cpp', '.h', '.html', '.xml', '.json', '.csv', '.docx', '.xlsx', '.pptx', '.pdf']: if file_ext in ['.docx', '.xlsx', '.pptx', '.pdf']: file_matches = self.search_in_office_file(full_path, pattern) else: file_matches = self.search_in_text_file(full_path, pattern) if file_matches: matches.append((file, f"压缩文件中的文件: {file}")) finally: shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: print(f"处理压缩文件失败 {filepath}: {str(e)}") return matches def detect_encoding(self, filepath): """改进的文件编码检测方法""" try: # 尝试读取文件前4KB进行编码检测 with open(filepath, 'rb') as f: raw_data = f.read(4096) # 使用chardet进行编码检测 result = chardet.detect(raw_data) # 优先使用检测到的编码,否则尝试常见编码 if result['confidence'] > 0.7: return result['encoding'] # 中文环境常用编码回退策略 common_encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1'] for encoding in common_encodings: try: # 尝试解码验证 raw_data.decode(encoding, errors='strict') return encoding except UnicodeDecodeError: continue # 默认使用UTF-8 return 'utf-8' except Exception: return 'utf-8' def is_binary(self, filepath): """检查文件是否为二进制文件""" try: with open(filepath, 'rb') as f: chunk = f.read(1024) if b'\0' in chunk: # 空字节是二进制文件的标志 return True # 检查高字节值 if any(byte >= 0x80 for byte in chunk): return True return False except: return False def stop_search(self): """停止当前搜索""" self.stop_requested = True self.status_label.config(text="正在停止搜索...") self.stop_button.config(state=tk.DISABLED) # 冻结进度条显示当前进度 self.progress_bar.config(mode='indeterminate' if self.progress_var.get() == 0 else 'determinate') def export_results(self): """导出搜索结果""" if not self.results: messagebox.showinfo("导出结果", "没有可导出的搜索结果") return file_path = filedialog.asksaveasfilename( title="保存搜索结果为", defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("文本文件", "*.txt")] ) if not file_path: return try: with open(file_path, 'w', encoding='utf-8') as f: # 写出CSV头部 f.write("文件路径,匹配行号,匹配内容\n") # 写出每项结果 for file, matches in self.results.items(): for line_num, match_content in matches: # 清理内容中的逗号 cleaned_content = match_content.replace('"', '""').replace(',', ';') f.write(f'"{file}",{line_num},"{cleaned_content}"\n') messagebox.showinfo("导出成功", f"搜索结果已保存到:\n{file_path}") except Exception as e: messagebox.showerror("导出错误", f"导出失败: {str(e)}") def show_file_content(self, event=None): """在预览区域显示文件内容""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] # 清空预览区域 self.content_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 显示文件路径标题 self.content_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.content_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.content_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项 for i, (line_num, content) in enumerate(matches, 1): self.content_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") self.content_text.insert(tk.END, f"{content}\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.content_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.content_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): self.content_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") self.content_text.insert(tk.END, f"{content}\n\n") # 处理文本文件 else: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) # 显示文件内容并高亮匹配 self.content_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.content_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.content_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.content_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.content_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.content_text.insert(tk.END, line[start_idx:]) else: self.content_text.insert(tk.END, line) except UnicodeDecodeError: self.content_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.content_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.content_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def open_selected_file(self, event=None): """用系统默认程序打开选中的文件""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] try: if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": # macOS subprocess.run(["open", filepath]) else: # Linux subprocess.run(["xdg-open", filepath]) except Exception as e: messagebox.showerror("打开文件失败", f"无法打开文件: {str(e)}") def open_file_location(self): """在文件资源管理器中打开文件所在位置""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] folder = os.path.dirname(filepath) try: if sys.platform == "win32": subprocess.run(["explorer", "/select,", filepath]) elif sys.platform == "darwin": # macOS subprocess.run(["open", "-R", filepath]) else: # Linux subprocess.run(["xdg-open", folder]) except Exception as e: messagebox.showerror("打开位置失败", f"无法打开位置: {str(e)}") def show_file_context_menu(self, event): """显示文件列表的右键菜单""" item = self.file_tree.identify_row(event.y) if item: self.file_tree.selection_set(item) self.file_menu.tk_popup(event.x_root, event.y_root) def copy_selected_text(self): """复制预览区域中选中的文本""" selected_text = self.content_text.get(tk.SEL_FIRST, tk.SEL_LAST) if selected_text: self.master.clipboard_clear() self.master.clipboard_append(selected_text) # 程序入口 if __name__ == "__main__": root = tk.Tk() app = FileSearchApp(root) # 添加图标(如果有) try: if sys.platform == "win32": root.iconbitmap("search_icon.ico") else: img = tk.PhotoImage(file="search_icon.png") root.iconphoto(True, img) except: pass root.mainloop() 检查问题,搜索出错
最新发布
09-11
import asyncio from concurrent.futures import ThreadPoolExecutor import os import time import numpy as np import traceback import cv2 import torch import csv from utils.general import ( check_img_size, non_max_suppression, apply_classifier, scale_coords, xyxy2xywh, strip_optimizer, set_logging) from models.common import DetectMultiBackend from utils.torch_utils import select_device from algo_config.common_config_data import * from algo_utils.code_filter import CodeFilter from algo_utils.code_identifier import CodeIdentifier # from algo_utils.uk_detector import UKDetector import json from math import ceil # from PIL import Image import logging import xml.etree.ElementTree as ET import xml.dom.minidom from utils.plots import Annotator, colors, save_one_box # 同类型code不同级别时,进行合并,key为主code,可考虑更新为可配置 BBOX_MERGE_CLS_INFO = {3: [2]} START_CUT_OFFX = 0 END_CUT_OFFX = 0 logger = logging.getLogger(__name__) def calc_file_size(file_path): if os.path.exists(file_path): file_size = os.path.getsize(file_path) else: file_size = -1 return file_size class Model(): def __init__(self, color_spec, device="", gray_spec=None, common_spec=None, conf_thres=0.45): from ultralytics import YOLO self.color_spec = color_spec self.gray_spec = gray_spec self.common_spec = common_spec self.augment = True self.conf_thres = conf_thres # 0.2 self.iou_thres = 0.5 self.agnostic_nms = False self.classes = None # 是否存储xml和img绘制,只有开启,绘制结果的参数才会生效 self.save_result = False # 是否绘制 self.draw_result_on_img = False imgsz = 1280 self.device_num = "cuda:" + str(device) self.device = select_device(self.device_num) # Initialize set_logging() # model = DetectMultiBackend(weights, device=self.device, dnn=False) model_s = YOLO(self.color_spec['split_model']) model_f = YOLO(self.color_spec['full_model']) self.model_s = model_s self.model_f = model_f self.imgsz = imgsz self.half = False # 获取模型中的标注code名称 self.code_name = list(self.model_s.names.values())+list(self.model_f.names.values()) # 增加unkown code,确保SD1UK始终在最后一个 # profile_preset.json的路径 self.profile_preset_path = self.color_spec['profile_preset_path'] # code过滤 self.code_filter = CodeFilter() # code区分合并 self.code_identifier = CodeIdentifier(self.code_name) # 未知缺陷定位检测 # self.u3_id = self.code_name.index(SD1U3) if SD1U3 in self.code_name else -1 # self.u4_id = self.code_name.index(SD1U4) if SD1U4 in self.code_name else -1 # self.ot_id = self.code_name.index(SD1OT) if SD1OT in self.code_name else -1 # self.uk_detector = UKDetector() def get_code_idx(self, code): return self.code_name.index(code) if code in self.code_name else -1 def update_save_draw_action(self, save_result, draw_result_on_img): """ 更新存储和绘制图像参数 """ self.save_result = save_result self.draw_result_on_img = draw_result_on_img def create_result_dict(self): """ 创建算法响应消息主体 :return: """ result = {} result.setdefault("status", 200) result.setdefault("message", "Success") result.setdefault("result", []) return result.copy() def dump_error_result(self, code, message): """ 异常代码 :param code: :param message: :return: """ self.result["status"] = code if not isinstance(message, str): message = repr(message) self.result["message"] = message def convert_box(self, box): """ 左上、右下转为左上、右上、右下、左下 :param box :return: """ new_box_list = [] for each_box in box: x1, y1, x2, y2 = each_box new_box_list.append([x1, y1, x2, y1, x2, y2, x1, y2]) return new_box_list def convert_code_name(self, cls_list): """Code转换 """ code_name_list = [] for cl in cls_list: code_name_list.append(self.code_name[int(cl)]) return code_name_list def merge_contours(self, contours, img_shape, padding=0): """ 合并多个轮廓的边界框 参数: contours: 轮廓列表 img_shape: 图像形状(H,W) padding: 在合并后的边界框周围添加的额外像素 返回: 合并后的边界框坐标(x, y, w, h) """ if not contours: return 0, 0, img_shape[1], img_shape[0] # 初始化边界框坐标 x_min = img_shape[1] y_min = img_shape[0] x_max = 0 y_max = 0 # 遍历所有轮廓,找到最大边界 for contour in contours: x, y, w, h = cv2.boundingRect(contour) x_min = min(x_min, x) y_min = min(y_min, y) x_max = max(x_max, x + w) y_max = max(y_max, y + h) # 添加padding x_min = max(0, x_min - padding) y_min = max(0, y_min - padding) x_max = min(img_shape[1], x_max + padding) y_max = min(img_shape[0], y_max + padding) return x_min, y_min, x_max - x_min, y_max - y_min def crop_black_borders(self, img, threshold=10, min_area=100000, padding=0): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化处理 _, thresh = cv2.threshold(gray, threshold, 255, cv2.THRESH_BINARY) # 找到非黑色区域的轮廓 contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 过滤掉太小的轮廓 filtered_contours = [c for c in contours if cv2.contourArea(c) > min_area] if not filtered_contours: return img # 如果没有找到符合条件的轮廓,返回原图 x, y, w, h = self.merge_contours(filtered_contours, gray.shape, padding) return x, y, w, h def merge_result(self, xyxy_rst, conf_rst, cls_rst): xyxy_results = [] conf_results = [] cls_results = [] # 类别数组 # cls_list = cls_rst.unique() cls_list = np.unique(cls_rst) cls_num = len(cls_list) xyxy_unique = [[] for j in range(cls_num)] conf_unique = [[] for j in range(cls_num)] cls_unique = [[] for j in range(cls_num)] # 按类别分组 for c in range(cls_num): for i in range(len(cls_rst)): if cls_rst[i] == cls_list[c]: xyxy_unique[c].append(xyxy_rst[i]) conf_unique[c].append(conf_rst[i]) cls_unique[c].append(cls_rst[i]) # 按类别合并: 计算iou for i in range(cls_num): box_result, conf_result, cls_result = self.custom_nms(xyxy_unique[i], conf_unique[i], cls_unique[i], 0.15) xyxy_results.append(box_result) conf_results.append(conf_result) cls_results.append(cls_result) # 将数据展平 xyxy_results_ = [element for sublist in xyxy_results for element in sublist] conf_results_ = [element for sublist in conf_results for element in sublist] cls_results_ = [element for sublist in cls_results for element in sublist] return xyxy_results_, conf_results_, cls_results_ def augment_image(self, img): clipLimit = 10 tileGridSize = (8, 8) if len(img.shape) == 2: # 单通道灰度图像 clahe = cv2.createCLAHE(clipLimit=clipLimit, tileGridSize=tileGridSize) enhanced_img = clahe.apply(img) enhanced_img = cv2.cvtCOLOR(enhanced_img, cv2.COLOR_GRAY2BGR) elif len(img.shape) == 3: # 三通道彩色图像 # 分离通道 b, g, r = cv2.split(img) # 对每个通道分别应用 CLAHE clahe = cv2.createCLAHE(clipLimit=clipLimit, tileGridSize=tileGridSize) enhanced_b = clahe.apply(b) enhanced_g = clahe.apply(g) enhanced_r = clahe.apply(r) # 合并通道 enhanced_img = cv2.merge((enhanced_b, enhanced_g, enhanced_r)) return enhanced_img def bbox_merge_cls(self, xyxy_results, conf_results, cls_results): ''' 同类型code合并框,以merge_info为配置进行合并 ''' def is_overlap(box1, box2): return not (box2[0] > box1[2] or box2[2] < box1[0] or box2[1] > box1[3] or box2[3] < box1[1]) def merge_bboxes(boxes): return [min(box[0] for box in boxes), min(box[1] for box in boxes), max(box[2] for box in boxes), max(box[3] for box in boxes)] to_delete = set() merged_boxes = [] merged_confidences = [] merged_codes = [] for key, vals in BBOX_MERGE_CLS_INFO.items(): # 找到需要过滤合并的code code_sub_indices = [i for i in range(len(cls_results)) if cls_results[i] in vals] code_main_indices = [i for i in range(len(cls_results)) if cls_results[i] == key] for j in code_main_indices: inter_boxes = [xyxy_results[j]] inter_confs = [conf_results[j]] for i in code_sub_indices: if is_overlap(xyxy_results[i], xyxy_results[j]): inter_boxes.append(xyxy_results[i]) inter_confs.append(conf_results[i]) to_delete.add(i) if len(inter_boxes) > 1: merged_box = merge_bboxes(inter_boxes) merged_boxes.append(merged_box) merged_confidences.append(conf_results[j]) merged_codes.append(key) to_delete.add(j) # 准备结果列表 new_boxes = [] new_confs = [] new_codes = [] # 添加未删除的原始框 for i in range(len(xyxy_results)): if i not in to_delete: new_boxes.append(xyxy_results[i]) new_confs.append(conf_results[i]) new_codes.append(cls_results[i]) # 添加合并后的框 new_boxes.extend(merged_boxes) new_confs.extend(merged_confidences) new_codes.extend(merged_codes) return new_boxes, new_confs, new_codes def custom_nms(self, boxes, confs, cls, iou_thresh): boxes = np.array(boxes) x1 = boxes[:, 0] y1 = boxes[:, 1] x2 = boxes[:, 2] y2 = boxes[:, 3] scores = np.array(confs) areas = (y2 - y1 + 1) * (x2 - x1 + 1) keep_boxes = [] index = scores.argsort()[::-1] box_result = [] conf_result = [] cls_result = [] while len(index) > 0: i = index[0] keep_boxes.append(i) x1_overlap = np.maximum(x1[i], x1[index[1:]]) y1_overlap = np.maximum(y1[i], y1[index[1:]]) x2_overlap = np.minimum(x2[i], x2[index[1:]]) y2_overlap = np.minimum(y2[i], y2[index[1:]]) # 计算重叠部分的面积,若没有不重叠部分则面积为 0 w = np.maximum(0, x2_overlap - x1_overlap + 1) h = np.maximum(0, y2_overlap - y1_overlap + 1) overlap_area = w * h ious = overlap_area / (areas[i] + areas[index[1:]] - overlap_area) idx = np.where(ious <= iou_thresh)[0] # 合并大框 big_idx = np.where(ious > iou_thresh)[0] union_index = index[big_idx + 1] if union_index.size == 1: lt_x = np.minimum(boxes[i][0], boxes[:, 0][union_index])[0] lt_y = np.minimum(boxes[i][1], boxes[:, 1][union_index])[0] rb_x = np.maximum(boxes[i][2], boxes[:, 2][union_index])[0] rb_y = np.maximum(boxes[i][3], boxes[:, 3][union_index])[0] elif union_index.size > 1: lt_x = np.min(np.minimum(boxes[i][0], boxes[:, 0][union_index])) lt_y = np.min(np.minimum(boxes[i][1], boxes[:, 1][union_index])) rb_x = np.max(np.maximum(boxes[i][2], boxes[:, 2][union_index])) rb_y = np.max(np.maximum(boxes[i][3], boxes[:, 3][union_index])) else: lt_x = boxes[i][0] lt_y = boxes[i][1] rb_x = boxes[i][2] rb_y = boxes[i][3] box_result.append([lt_x, lt_y, rb_x, rb_y]) conf_result.append(scores[i]) cls_result.append(cls[i]) index = index[idx + 1] return box_result, conf_result, cls_result # 执行推理 def pytorch_infer(self, im): visualize = False augment = False # pred = self.model(im, augment=augment, visualize=visualize) pred = self.model.predict(im, save=False) # NMS conf_thres = 0.15 iou_thres = 0.6 max_det = 100 classes = None agnostic_nms = False # pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) return pred def calc_code_conf_threshold(self, profile): code_id = [] code_conf_thre = [] code_pr_thre = [] if len(profile) != 0: for key, code in profile.items(): if not key in self.code_name: continue code_id.append(self.code_name.index(key)) code_conf_thre.append(code["th"]) code_pr_thre.append(code["pr"]) return code_id, code_conf_thre, code_pr_thre def transform_bbox(self, bbox_resized, origin_size, resized_size, is_need_rotate): W_origin, H_origin = origin_size resized_width, resized_height = resized_size # 缩放因子 if is_need_rotate: scale_x = H_origin / resized_width scale_y = W_origin / resized_height else: scale_y = H_origin / resized_width scale_x = W_origin / resized_height # 提取resized bbox坐标 xmin_r, ymin_r, xmax_r, ymax_r = bbox_resized # 转换到旋转后的坐标系 x_rot_min = xmin_r * scale_x y_rot_min = ymin_r * scale_y x_rot_max = xmax_r * scale_x y_rot_max = ymax_r * scale_y if is_need_rotate: # 限制坐标范围 x_rot_min = max(0, min(x_rot_min, H_origin - 1)) y_rot_min = max(0, min(y_rot_min, W_origin - 1)) x_rot_max = max(0, min(x_rot_max, H_origin - 1)) y_rot_max = max(0, min(y_rot_max, W_origin - 1)) # 逆旋转到原始坐标系 x_origin_left = W_origin - 1 - y_rot_max x_origin_right = W_origin - 1 - y_rot_min ymin_origin = x_rot_min ymax_origin = x_rot_max # 确定min和max xmin_origin = min(x_origin_left, x_origin_right) xmax_origin = max(x_origin_left, x_origin_right) ymin_origin = min(ymin_origin, ymax_origin) ymax_origin = max(ymin_origin, ymax_origin) # 确保不超出原始图像范围 xmin_origin = max(0, min(xmin_origin, W_origin - 1)) xmax_origin = max(0, min(xmax_origin, W_origin - 1)) ymin_origin = max(0, min(ymin_origin, H_origin - 1)) ymax_origin = max(0, min(ymax_origin, H_origin - 1)) else: # 限制坐标范围 x_rot_min = max(0, min(x_rot_min, W_origin - 1)) y_rot_min = max(0, min(y_rot_min, H_origin - 1)) x_rot_max = max(0, min(x_rot_max, W_origin - 1)) y_rot_max = max(0, min(y_rot_max, H_origin - 1)) xmin_origin = x_rot_min xmax_origin = x_rot_max ymin_origin = y_rot_min ymax_origin = y_rot_max return (int(xmin_origin), int(ymin_origin), int(xmax_origin), int(ymax_origin)) def infer_cv_detect(self, origin_image): ''' 检测黑框等CV直接可检出缺陷 ''' return self.code_identifier.identify_black_rect_as_OT(origin_image) def temp_filter_code(self, img_path, pred_cls): """临时过滤,如果有对应code则不输出 """ for k, temp_code_list in TEMP_FILTER_DICT.items(): if os.path.basename(img_path).startswith(k): for temp_code in temp_code_list: temp_idx = self.get_code_idx(temp_code) if temp_idx == pred_cls: return True return False def infer_od(self, origin_image, origin_image_before_rot, source, code_id, code_conf_thre, code_pr_thre, Image_Width, Image_Height, is_need_rotate, target_size=(1280, 1280)): """目标检测推理 传入是resize +(可能旋转)的图像,所以需要传入原始长宽 可能多个处理均需要旋转,所以传入旋转后的图,只在外部旋转一次 """ # Image_Width = origin_image.shape[1] # Image_Height = origin_image.shape[0] xyxy_results = [] conf_results = [] cls_results = [] # 图像 image = origin_image.copy() infer_full = True if infer_full: # Convert image = cv2.resize(image, target_size, interpolation=cv2.INTER_LINEAR) img = image.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB img = np.ascontiguousarray(img) im = torch.from_numpy(img).to(self.device) im = im.half() if self.half else im.float() # uint8 to fp16/32 im /= 255 # 0 - 255 to 0.0 - 1.0 if len(im.shape) == 3: im = im[None] # expand for batch dim # 执行推理 pred = self.pytorch_infer(im) for i, det in enumerate(pred): for *xyxy, conf, cls in zip(det.boxes.xyxy, det.boxes.conf, det.boxes.cls): temp_xyxy = [] # 判断检出的code是否满足置信度阈值,以及是否过滤项 if len(code_id) > 0: clsID_pred = int(cls.cpu().detach()) # 过滤特定产品code pr_threshold = code_pr_thre[code_id.index(clsID_pred)] # 设置为10000的为不需要检出的code,通常是用于抑制过检的OK Code if pr_threshold == 10000: continue if TEMP_FILTER_ENABLE and self.temp_filter_code(source, clsID_pred): continue conf_pred = float(conf.cpu().detach()) conf_threshold = code_conf_thre[code_id.index(clsID_pred)] if conf_pred < conf_threshold: continue else: conf_pred = float(conf.cpu().detach()) clsID_pred = int(cls.cpu().detach()) for _xyxy in xyxy: temp_xyxy = [int(ts.item()) for ts in _xyxy.cpu().detach()] # temp_xyxy.append(int(_xyxy.cpu().detach())) # 图像转换回bbox cur_xyxy = self.transform_bbox(temp_xyxy, (Image_Width, Image_Height), target_size, is_need_rotate) xyxy_results.append(cur_xyxy) conf_results.append(round(conf_pred, 4)) cls_results.append(clsID_pred) # 合并相同code相交的情况 xyxy_results, conf_results, cls_results = self.code_identifier.merge_codes_bbox(xyxy_results, conf_results, cls_results) # 合并不同code重叠的情况 if len(xyxy_results) > 0: xyxy_results, conf_results, cls_results = self.code_identifier.merge_overlap_codes(xyxy_results, conf_results, cls_results, MERGE_DIFF_CODE_BBOX_IOU_THRESHOLD) # 更新P2/P3 code,此处的code需要提取图像,且bbox是经过transform之后的 cls_results, p2_max_bboxes = self.code_identifier.identify_P2P3(origin_image_before_rot, xyxy_results, cls_results) # exists_u3u4 = self.u3_id in cls_results or self.u4_id in cls_results # 此处的坐标已变化为原始图,所以传入的长宽也需要变换之前的 xyxy_results, conf_results, cls_results = self.code_filter.filter_edge_code(xyxy_results, conf_results, cls_results, Image_Width, Image_Height) return xyxy_results, conf_results, cls_results def infer_cut(self, origin_image, code_id, code_conf_thre, code_pr_thre): def post_processing(pred): one_xyxy, one_conf, one_cls = [], [], [] for _, det in enumerate(pred): for *xyxy, conf, cls in zip(det.boxes.xyxy, det.boxes.conf, det.boxes.cls): # 判断检出的code是否满足置信度阈值 if len(code_id) > 0: clsID_pred = int(cls.cpu().detach()) if not clsID_pred in code_id: # 没有在待处理的id中 continue pr_threshold = code_pr_thre[code_id.index(clsID_pred)] conf_pred = float(conf.cpu().detach()) conf_threshold = code_conf_thre[code_id.index(clsID_pred)] if conf_pred < conf_threshold: continue else: conf_pred = float(conf.cpu().detach()) clsID_pred = int(cls.cpu().detach()) # 写入筛选后的结果 temp_xyxy = [] temp_xyxy.append(int(xyxy[0][0].cpu().detach())) temp_xyxy.append(int(xyxy[0][1].cpu().detach())) temp_xyxy.append(int(xyxy[0][2].cpu().detach())) temp_xyxy.append(int(xyxy[0][3].cpu().detach())) # 各类边缘缺陷过滤 # if self.filter_od_edge_code(edge_code_filter, clsID_pred, temp_xyxy): # continue if round(conf_pred, 4) > self.conf_thres: one_xyxy.append(temp_xyxy) one_conf.append(round(conf_pred, 4)) one_cls.append(clsID_pred) return one_xyxy, one_conf, one_cls # 图像增强 image = self.augment_image(origin_image) model_size = self.imgsz overlap = 0.25 overlap_size = int(overlap / 2.0 * model_size) valid_lt_x, valid_lt_y, w, h = self.crop_black_borders(origin_image) valid_rb_x, valid_rb_y = valid_lt_x+w, valid_lt_y+h # 实际切图,第一张保持切图size,其他均回退overlap_size,所以除第一张外, # 实际坐标位移变化为model_size-overlap_size h_index = ceil((h - model_size) / (model_size - overlap_size)) + 1 w_index = ceil((w - model_size) / (model_size - overlap_size)) + 1 xyxy_rst = [] conf_rst = [] cls_rst = [] w_with_end_offx = w - END_CUT_OFFX for i in range(h_index): for j in range(w_index): start_y = max(int(i * model_size - overlap_size * i) + valid_lt_y, 0) start_x = max(int(j * model_size - overlap_size * j) + valid_lt_x, 0) end_x = start_x + model_size end_y = start_y + model_size # 超限位置 if end_x > valid_rb_x: over_size = end_x - w_with_end_offx end_x = w_with_end_offx start_x = start_x - over_size if end_y > valid_rb_y: over_size = end_y - h end_y = h start_y = start_y - over_size # 截图 img_tailor = image[int(start_y):int(end_y), int(start_x):int(end_x)] img = img_tailor.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB img = np.ascontiguousarray(img) im = torch.from_numpy(img).to(self.device) im = im.half() if self.half else im.float() # uint8 to fp16/32 im /= 255 # 0 - 255 to 0.0 - 1.0 if len(im.shape) == 3: im = im[None] # expand for batch dim # 执行推理 pred = self.model_s.predict(im, save=False) one_xyxy, one_conf, one_cls = post_processing(pred) one_xyxy = [[xyxy[0]+start_x, xyxy[1]+start_y, xyxy[2]+start_x, xyxy[3]+start_y] for xyxy in one_xyxy] # if self.draw_result_on_img: # img_name = str(i) + '_' + str(j) + '.jpg' # img_tailor = np.ascontiguousarray(img_tailor) # self.draw_result(img_tailor, one_xyxy, one_conf, one_cls, img_name, self.save_root+'/CUT') xyxy_rst += one_xyxy conf_rst += one_conf cls_rst += one_cls im_full = cv2.resize(image, (self.imgsz, self.imgsz), interpolation=cv2.INTER_LINEAR) im_full = im_full.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB im_full = np.ascontiguousarray(im_full) im = torch.from_numpy(im_full).to(self.device) im = im.half() if self.half else im.float() # uint8 to fp16/32 im /= 255 # 0 - 255 to 0.0 - 1.0 if len(im.shape) == 3: im = im[None] # expand for batch dim # 执行推理 pred = self.model_f.predict(im, save=False) one_xyxy, one_conf, one_cls = post_processing(pred) one_cls = [cls+len(self.model_s.names) for cls in one_cls] one_xyxy = [[xyxy[0]/self.imgsz*w, xyxy[1]/self.imgsz*h, xyxy[2]/self.imgsz*w, xyxy[3]/self.imgsz*h] for xyxy in one_xyxy] xyxy_rst += one_xyxy conf_rst += one_conf cls_rst += one_cls # 缺陷结果合并 xyxy_results, conf_results, cls_results = self.merge_result(xyxy_rst, conf_rst, cls_rst) # 合并相交的STM0/1 xyxy_results, conf_results, cls_results = self.bbox_merge_cls(xyxy_results, conf_results, cls_results) pred_sorted, pred_unique, cls_list = self.sort_by_conf(xyxy_results, conf_results, cls_results) pred_sorted[0] = self.convert_box(pred_sorted[0]) pred_sorted[2] = self.convert_code_name(pred_sorted[2]) return pred_sorted, pred_unique, cls_list, xyxy_results, conf_results, cls_results def split_get_result(self, source, origin_image, profile): """ 单张图片推理,加载图片并且执行前向传播,得到该张图片预测结果 :param source: :return: """ # 解析code阈值 code_id, code_conf_thre, code_pr_thre = self.calc_code_conf_threshold(profile) xyxy_results = [] conf_results = [] cls_results = [] # origin_image = cv2.imdecode(np.fromfile(source, dtype=np.uint8), cv2.IMREAD_COLOR) Image_Width = origin_image.shape[1] Image_Height = origin_image.shape[0] origin_image_before_rot = origin_image.copy() t1 = time.time() # 是否需要旋转处理的图像 is_need_rotate = Image_Width < Image_Height if is_need_rotate: origin_image = cv2.rotate(origin_image, cv2.ROTATE_90_COUNTERCLOCKWISE) with ThreadPoolExecutor(max_workers=2) as executor: future1 = executor.submit(self.infer_od, origin_image, origin_image_before_rot, source, code_id, code_conf_thre, code_pr_thre, Image_Width, Image_Height, is_need_rotate) od_result = future1.result() xyxy_results, conf_results, cls_results = od_result pred_sorted, pred_unique, cls_list = self.sort_by_conf(xyxy_results, conf_results, cls_results) pred_sorted[0] = self.convert_box(pred_sorted[0]) pred_sorted[2] = self.convert_code_name(pred_sorted[2]) # 是否保存 if self.save_result: # 绘制存储 if len(cls_results) == 0: concat_save_path = f"{self.save_root}/OK" else: concat_save_path = f"{self.save_root}/NG" xml_img_folder = os.path.dirname(source) img_name = os.path.basename(source) xml_img_filename = img_name if self.draw_result_on_img: self.draw_result(origin_image_before_rot, xyxy_results, conf_results, cls_results, img_name, concat_save_path) # 存储标注信息 self.dump_xml(xml_img_folder, xml_img_filename, origin_image_before_rot.shape, xyxy_results, conf_results, cls_results, concat_save_path) # 如果没有code if len(conf_results) < 1: # SD1F1为无缺陷 cls_results = ['difficult'] conf_results = ['1.0'] m_H = origin_image_before_rot.shape[0] m_W = origin_image_before_rot.shape[1] xyxy_results = [[0, 0, int(m_W), 0, int(m_W), int(m_H), 0, int(m_H)]] pred_sorted = [xyxy_results, conf_results, cls_results] pred_unique = [] cls_list = [] print("cost:", time.time() - t1) return pred_sorted, pred_unique, cls_list, Image_Width, Image_Height def get_result(self, source, origin_image, profile): """ 单张图片推理,加载图片并且执行前向传播,得到该张图片预测结果 :param source: :return: """ # 解析code阈值 code_id, code_conf_thre, code_pr_thre = self.calc_code_conf_threshold(profile) xyxy_results = [] conf_results = [] cls_results = [] # origin_image = cv2.imdecode(np.fromfile(source, dtype=np.uint8), cv2.IMREAD_COLOR) Image_Width = origin_image.shape[1] Image_Height = origin_image.shape[0] origin_image_before_rot = origin_image.copy() t1 = time.time() # 是否需要旋转处理的图像 # is_need_rotate = Image_Width < Image_Height # if is_need_rotate: # origin_image = cv2.rotate(origin_image, cv2.ROTATE_90_COUNTERCLOCKWISE) # with ThreadPoolExecutor(max_workers=8) as executor: # future1 = executor.submit(self.infer_cut, origin_image, code_id, code_conf_thre, code_pr_thre) # cut_result = future1.result() cut_result = self.infer_cut(origin_image, code_id, code_conf_thre, code_pr_thre) pred_sorted, pred_unique, cls_list, xyxy_results, conf_results, cls_results = cut_result # 如果没有code if len(conf_results) < 1: # SD1F1为无缺陷 cls_results = ['OTHER'] conf_results = [0.9999] xyxy_results = [[0, 0, Image_Width, Image_Height]] pred_sorted = [xyxy_results, conf_results, cls_results] pred_unique = [] cls_list = [] # 是否保存 if self.save_result: # 绘制存储 concat_save_path = f"{self.save_root}/RES" xml_img_folder = os.path.dirname(source) img_name = os.path.basename(source) xml_img_filename = img_name if self.draw_result_on_img: self.draw_result(origin_image_before_rot, xyxy_results, conf_results, cls_results, img_name, concat_save_path) # 存储标注信息 self.dump_xml(xml_img_folder, xml_img_filename, origin_image_before_rot.shape, xyxy_results, conf_results, cls_results, concat_save_path) print("cost:", time.time() - t1) return pred_sorted, pred_unique, cls_list, Image_Width, Image_Height def draw_result(self, image, xyxy_rst, conf_rst, cls_rst, img_name, concat_save_path): save_path = concat_save_path if not os.path.exists(save_path): os.makedirs(save_path) image_save_path = f"{save_path}/{img_name}" line_thickness = 1 annotator = Annotator(image, line_width=line_thickness, example=str(self.code_name)) for i in range(len(cls_rst)): if cls_rst[i] == 'OTHER': c = len(self.code_name) label = f'{'OTHER'} {conf_rst[i]:.2f}' else: c = int(cls_rst[i]) # integer class label = f'{self.code_name[c]} {conf_rst[i]:.2f}' annotator.box_label(xyxy_rst[i], label, color=colors(c, True)) image = annotator.result() cv2.imencode('.jpg', image)[1].tofile(image_save_path) def dump_xml(self, xml_folder, xml_filename, shape, xyxy_results, conf_results, cls_results, concat_save_path): annotation = ET.Element("annotation") # 添加子元素 folder = ET.SubElement(annotation, "folder") folder.text = xml_folder filename = ET.SubElement(annotation, "filename") filename.text = xml_filename segmented = ET.SubElement(annotation, "segmented") segmented.text = str(0) source = ET.SubElement(annotation, "source") database = ET.SubElement(source, "database") database.text = 'Unknown' size = ET.SubElement(annotation, "size") width = ET.SubElement(size, "width") width.text = str(shape[1]) height = ET.SubElement(size, "height") height.text = str(shape[0]) depth = ET.SubElement(size, "depth") depth.text = str(shape[2]) # 填充标注信息 for i in range(len(cls_results)): if cls_results[i] == 'OTHER': label = 'OTHER' else: label = self.code_name[cls_results[i]] m_name = label # m_difficult = label[-1] m_subConf = round(conf_results[i], 3) m_xmin = xyxy_results[i][0] m_ymin = xyxy_results[i][1] m_xmax = xyxy_results[i][2] m_ymax = xyxy_results[i][3] m_object = ET.SubElement(annotation, "object") name = ET.SubElement(m_object, "name") name.text = m_name pose = ET.SubElement(m_object, "pose") pose.text = 'Unspecifed' truncated = ET.SubElement(m_object, "truncated") truncated.text = str(0) difficult = ET.SubElement(m_object, "difficult") difficult.text = str(0) contrast = ET.SubElement(m_object, "contrast") contrast.text = str(0) luminance = ET.SubElement(m_object, "luminance") luminance.text = str(0) subConf = ET.SubElement(m_object, "subConf") subConf.text = str(m_subConf) bndbox = ET.SubElement(m_object, "bndbox") xmin = ET.SubElement(bndbox, "xmin") xmin.text = str(int(m_xmin)) ymin = ET.SubElement(bndbox, "ymin") ymin.text = str(int(m_ymin)) xmax = ET.SubElement(bndbox, "xmax") xmax.text = str(int(m_xmax)) ymax = ET.SubElement(bndbox, "ymax") ymax.text = str(int(m_ymax)) # 将 XML 结构保存为文件 save_path = concat_save_path if not os.path.exists(save_path): os.makedirs(save_path) xml_file = xml_filename.split('.')[0] xml_filepath = f"{save_path}/{xml_file}.xml" tree = ET.ElementTree(annotation) tree.write(xml_filepath, encoding="utf-8", xml_declaration=True) # 使用 xml.dom.minidom 格式化 XML 文件 dom = xml.dom.minidom.parse(xml_filepath) with open(xml_filepath, "w", encoding="utf-8") as f: f.write(dom.toprettyxml(indent=" ")) # 使用四个空格作为缩进 def get_online_anomaly_conf_threshold(self, profile, label): online_conf_thr = 0.1 try: if label in profile and "th" in profile[label]: online_conf_thr = profile[label]["th"] except Exception as e: print(f"Get anomaly conf threshold error:{e}") pass return online_conf_thr def sort_by_priority(self, profile, pred_unique, cls_list): # 判断优先级文档是否存在\内容是否不全 if len(profile) == 0: box_result = [[0, 0, 20, 0, 20, 20, 0, 20]] conf_result = [1.0] cls_result = ['ISSUE'] return box_result, conf_result, cls_result xyxy_unique = pred_unique[0] conf_unique = pred_unique[1] cls_unique = pred_unique[2] # 将code_list转换为code_name_list if not isinstance(cls_list[0], str): code_name_list = self.convert_code_name(cls_list) # 根据优先级,返回优先级最高的code box_result = [] conf_result = [] cls_result = [] for key, value in profile.items(): if key not in code_name_list: continue index = code_name_list.index(key) temp_box = [xyxy_unique[index][0]] if len(temp_box[0]) == 4: box_result = self.convert_box(temp_box) else: box_result = temp_box[0] conf_result.append(conf_unique[index][0]) cls_result.append(key) break return box_result, conf_result, cls_result def sort_by_conf(self, xyxy_results, conf_results, cls_results): ''' 1、先计算code_list 2、再对每一类code按照conf排序 3、返回排序后的结果和code_list ''' # 类别数组 # cls_list = cls_rst.unique() cls_list = np.unique(cls_results) cls_num = len(cls_list) xyxy_unique = [[] for j in range(cls_num)] conf_unique = [[] for j in range(cls_num)] cls_unique = [[] for j in range(cls_num)] # 按类别分组 for c in range(cls_num): temp_xyxy = [] temp_conf = [] temp_cls = [] for i in range(len(cls_results)): if cls_results[i] == cls_list[c]: temp_xyxy.append(xyxy_results[i]) temp_conf.append(conf_results[i]) temp_cls.append(cls_results[i]) sorted_list = sorted(temp_conf, reverse=True) indexes = [temp_conf.index(x) for x in sorted_list] for index in indexes: xyxy_unique[c].append(temp_xyxy[index]) conf_unique[c].append(temp_conf[index]) cls_unique[c].append(float(temp_cls[index])) # 也可以将unique中的元素展平 xyxy_results_ = [element for sublist in xyxy_unique for element in sublist] conf_results_ = [element for sublist in conf_unique for element in sublist] cls_results_ = [element for sublist in cls_unique for element in sublist] return [xyxy_results_, conf_results_, cls_results_], [xyxy_unique, conf_unique, cls_unique], cls_list.tolist() def get_final_result(self, pred_result): """ 计算该张图片的最终结果 (接口测试阶段,默认取第一个结果为最终结果) :param pred_result: :return: """ box_list, conf_list, code_list = pred_result final_result_dic = {} final_result_dic.setdefault("img_cls", [code_list[0]]) final_result_dic.setdefault("img_box", [box_list[0]]) final_result_dic.setdefault("img_score", [conf_list[0]]) return final_result_dic def get_group_final(self, pred_result, gid, savepath): """ 计算该批次任务的最终结果 (接口测试阶段,默认取最后一张图片的第一个结果作为最终结果) :param pred_result: :param gid: :param savepath: :return: """ box_list, conf_list, code_list = pred_result group_final_dic = {} group_final_dic.setdefault("img_cls", [code_list[0]]) if len(box_list[0]) == 1: group_final_dic.setdefault("img_box", box_list[0]) else: group_final_dic.setdefault("img_box", [box_list[0]]) group_final_dic.setdefault("img_score", [conf_list[0]]) group_final_dic.setdefault("gid", gid) group_final_dic.setdefault("defect", len(code_list)) group_final_dic.setdefault("type", "Final") group_final_dic.setdefault("savepath", savepath) return group_final_dic def check_files(self, img_json, retry_times=1): """ 执行推理任务前检查必要的文件是否存在 :param img_json: :return: """ cur_file_path = "" while retry_times >= 0: try: img_info_list = img_json["image"] # 实际任务单次只会有一张,所以可以读图后返回 for each in img_info_list: img_path = each["path"] cur_file_path = img_path raw_img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), cv2.IMREAD_COLOR) return raw_img except Exception as e: retry_times -= 1 stack_trace = traceback.format_exc() file_size = calc_file_size(cur_file_path) # 读图失败优先重试,达到重试次数再报错 if retry_times >= 0: logger.info( f"==SD1 Algo==: Image read error, Retry!!:{cur_file_path}, size:{file_size}, {stack_trace}") time.sleep(RETRY_CHECK_SLEEP_TIME) else: self.dump_error_result(610, 'Image read error') logger.error(f"==SD1 Algo==: Image read error:{stack_trace}") logger.info(f"==SD1 Algo==: {cur_file_path}: size:{file_size}") return [] def check_json(self, profile_preset_path): """ 执行推理任务前检查必要的文件是否存在 :param img_json: :return: """ try: with open(profile_preset_path, 'r', encoding='utf-8') as file: profile_preset = json.load(file) except: profile_preset = {} self.dump_error_result(911, 'Lack of profile_preset.jsom') return profile_preset def parse_code_priority(self, img_json, profile_preset): ''' 如果csv完整,就使用csv信息 如果csv信息不完整,就使用json信息补充 如果csv为空,就直接使用json配置 ''' profile = {} try: profile_path = img_json["info"]["profile_path"] with open(profile_path, 'r', encoding="utf-8") as f: reader = csv.reader(f) csv_code = [] for row in reader: if row[0] != 'DEFECT_SIZE': priority = row[1] confidence = row[2] defcet_name = row[3] # 有code名,缺失优先级、置信度,且在预置的json中,则使用预置的值 if priority == '': # csv优先级为空,使用默认值,如果默认值没有,则设置为666 if len(profile_preset) > 0 and defcet_name in profile_preset.keys(): priority = profile_preset[defcet_name]["PRIORITY"] else: priority = 666 if confidence == '': if len(profile_preset) > 0 and defcet_name in profile_preset.keys(): confidence = profile_preset[defcet_name]["CONFIDENCE"] else: confidence = 0.2 profile[defcet_name] = {} profile[defcet_name].setdefault('th', float(confidence)) profile[defcet_name].setdefault('pr', int(priority)) # csv信息不完整,使用json信息填充 if len(profile) < len(profile_preset): for key, value in profile_preset.items(): if key not in profile.keys(): profile[key] = {} profile[key].setdefault('th', float(value['CONFIDENCE'])) profile[key].setdefault('pr', int(value['PRIORITY'])) elif len(profile_preset) < 0: # 如果没有json文件 if len(profile) < len(self.code_name): for c_name in self.code_name: if c_name not in profile.keys(): profile[c_name] = {} profile[c_name].setdefault('th', 0.15) profile[c_name].setdefault('pr', 666) except Exception as e: if len(profile_preset) > 0: for key, value in profile_preset.items(): profile[key] = {} profile[key].setdefault('th', float(value['CONFIDENCE'])) profile[key].setdefault('pr', int(value['PRIORITY'])) else: for c_name in self.code_name: if c_name not in profile.keys(): profile[c_name] = {} profile[c_name].setdefault('th', 0.15) profile[c_name].setdefault('pr', 1) # 按照pr等级排序 profile = dict(sorted(profile.items(), key=lambda x: x[1]['pr'])) return profile def infer(self, img_json, testing=False): """ infrence main program :param img_json: :return: reulst(有固定格式,按照接口规范返回) """ # 创建推理响应主体 self.result = self.create_result_dict() pattern_results_list = [] img_info_list = img_json['image'] self.save_root = img_json["info"]["saveROOT_PATH"] # 检查图片及配置文件是否存在 origin_image = self.check_files(img_json, RETRY_TIMES) # 检查profile_preset.json文件是否存在 profile_preset = self.check_json(self.profile_preset_path) if self.result['status'] == 200: # 读取用户配置文件 # 获取code优先级 profile = self.parse_code_priority(img_json, profile_preset) p_box_list = [] p_conf_list = [] p_cls_list = [] p_code_list = [] for each in img_info_list: img_path = each["path"] # 预测结果 # if not testing: # pred_result, pred_unique, cls_list, Image_Width, Image_Height = self.get_result(img_path, origin_image, profile) # else: # pred_result, pred_unique, cls_list, Image_Width, Image_Height = self.split_get_result(img_path, origin_image, profile) pred_result, pred_unique, cls_list, Image_Width, Image_Height = self.get_result(img_path, origin_image, profile) # 结果优先级排序 if len(cls_list) > 0: priority_result = self.sort_by_priority(profile, pred_unique, cls_list) else: priority_result = pred_result final_result = self.get_final_result(priority_result) # 单张图片的推理结果 pattern_results = {} pattern_results.setdefault("img_cls", pred_result[2]) pattern_results.setdefault("img_box", pred_result[0]) pattern_results.setdefault("img_score", pred_result[1]) if "uid" in each: pattern_results.setdefault("uid", each["uid"]) if "gid" in each: pattern_results.setdefault("gid", each["gid"]) pattern_results.setdefault("defect", len(pred_result[2])) if "type" in each: pattern_results.setdefault("type", each["type"]) pattern_results.setdefault("savepath", img_json["info"]["saveROOT_PATH"]) pattern_results.setdefault("final", final_result) # 业务特殊需求返回 ATTR = {"IMAGE_PIXEL_WIDTH": Image_Width, "IMAGE_PIXEL_HEIGHT": Image_Height} pattern_results.setdefault("ATTR", ATTR) pattern_results_list.append(pattern_results) # 用于group寻优 if len(cls_list) > 0 and priority_result[2][0] != 'ISSUE': p_box_list.append([priority_result[0]]) p_conf_list.append(priority_result[1]) p_cls_list.append(priority_result[2]) p_code_list.append(self.code_name.index(priority_result[2][0])) else: p_box_list.append([priority_result[0]]) p_conf_list.append(priority_result[1]) p_cls_list.append(priority_result[2]) p_code_list.append(-1) # 多张图最优结果中再次取最优 if len(np.unique(p_code_list)) == 1 and np.unique(p_code_list) == -1: priority_result = [p_box_list[0], p_conf_list[0], p_cls_list[0]] p_code_list = [] else: length = len(p_code_list) del_index = 0 while del_index < length: if p_code_list[del_index] == -1: del p_box_list[del_index] del p_conf_list[del_index] del p_cls_list[del_index] del p_code_list[del_index] length -= 1 else: del_index += 1 if len(p_code_list) > 0: p_pred_unique = [p_box_list, p_conf_list, p_cls_list] priority_result = self.sort_by_priority(profile, p_pred_unique, p_code_list) if 'gid' in img_json['image'][0]: group_result = self.get_group_final(priority_result, img_json['image'][0]['gid'], img_json['info']['saveROOT_PATH']) pattern_results_list.append(group_result) self.result["result"] = pattern_results_list return self.result 这段代码详细解释一下
09-06
由于没有给出具体的包含目标检测推理功能,且使用了 YOLO 模型、多线程、图像增强、黑边裁剪等技术的 Python 代码,下面给出一个示例代码并进行详细解释。 ```python import cv2 import torch import threading from PIL import Image, ImageEnhance import numpy as np # 图像增强函数 def augment_image(image): # 转换为 PIL 图像 pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) # 亮度增强 enhancer = ImageEnhance.Brightness(pil_image) brightened_image = enhancer.enhance(1.5) # 转换回 OpenCV 图像 return cv2.cvtColor(np.array(brightened_image), cv2.COLOR_RGB2BGR) # 黑边裁剪函数 def crop_black_borders(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: cnt = max(contours, key=cv2.contourArea) x, y, w, h = cv2.boundingRect(cnt) return image[y:y + h, x:x + w] return image # 目标检测推理函数 def detect_objects(image, model): results = model(image) return results # 多线程处理函数 def process_image_thread(image, model, result_list, index): augmented_image = augment_image(image) cropped_image = crop_black_borders(augmented_image) results = detect_objects(cropped_image, model) result_list[index] = results # 主函数 def main(): # 加载 YOLO 模型 model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) # 读取图像 image = cv2.imread('test.jpg') # 多线程处理 num_threads = 2 result_list = [None] * num_threads threads = [] for i in range(num_threads): thread = threading.Thread(target=process_image_thread, args=(image, model, result_list, i)) threads.append(thread) thread.start() for thread in threads: thread.join() # 输出结果 for results in result_list: print(results.pandas().xyxy[0]) if __name__ == "__main__": main() ``` ### 代码解释 1. **导入必要的库**: - `cv2`:OpenCV 库,用于图像处理。 - `torch`:PyTorch 库,用于深度学习模型的加载和推理。 - `threading`:用于实现多线程处理。 - `PIL` 库中的 `Image` 和 `ImageEnhance`:用于图像增强。 - `numpy`:用于数组操作。 2. **图像增强函数 `augment_image`**: - 将 OpenCV 图像转换为 PIL 图像。 - 使用 `ImageEnhance.Brightness` 增强图像的亮度。 - 将增强后的 PIL 图像转换回 OpenCV 图像。 3. **黑边裁剪函数 `crop_black_borders`**: - 将图像转换为灰度图。 - 使用阈值处理得到二值图像。 - 查找轮廓,找到最大的轮廓并获取其边界框。 - 根据边界框裁剪图像。 4. **目标检测推理函数 `detect_objects`**: - 使用加载的 YOLO 模型对图像进行目标检测。 5. **多线程处理函数 `process_image_thread`**: - 对图像进行增强和黑边裁剪处理。 - 调用目标检测推理函数进行检测。 - 将结果存储在结果列表中。 6. **主函数 `main`**: - 加载预训练的 YOLOv5s 模型。 - 读取测试图像。 - 创建多个线程,每个线程处理一次图像。 - 等待所有线程完成。 - 输出检测结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值