活动介绍

from paddleocr import PaddleOCR import cv2 import warnings # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) # 使用最新推荐方式初始化OCR引擎 ocr = PaddleOCR( text_detection_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_det", text_recognition_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_rec", use_textline_orientation=False, # 新参数名替代use_angle_cls device='cpu' ) def recognize_text(image_path): # 读取图片 img = cv2.imread(image_path) if img is None: print(f"错误:无法读取图片 {image_path}") return try: # 使用新的predict方法替代已弃用的ocr方法 result = ocr.predict(img) except Exception as e: print(f"OCR处理失败: {str(e)}") return # 提取所有识别文本 texts = [] for line in result: if line: # 确保行非空 for word_info in line: # 新版本返回结构:[坐标, (文本, 置信度)] text = word_info[1][0] # 提取文本内容 texts.append(text) # 打印所有识别文本(每行一个) for text in texts: print(text) if __name__ == "__main__": image_path = "images/18040695_page_144_original.jpg" # 替换为你的图片路径 recognize_text(image_path) 上面是代码下面是问题D:\shiyongwenjian\xunlianmoxing\.venv\Scripts\python.exe D:\shiyongwenjian\xunlianmoxing\实验8.py ��Ϣ: ���ṩ��ģʽ�޷��ҵ��ļ��� D:\shiyongwenjian\xunlianmoxing\.venv\Lib\site-packages\paddle\utils\cpp_extension\extension_utils.py:715: UserWarning: No ccache found. Please be aware that recompiling all source files may be required. You can download and install ccache from: https://github.com/ccache/ccache/blob/master/doc/INSTALL.md warnings.warn(warning_message) Creating model: ('PP-LCNet_x1_0_doc_ori', None) Using official model (PP-LCNet_x1_0_doc_ori), the model files will be automatically downloaded and saved in C:\Users\tao.yu02\.paddlex\official_models. Creating model: ('UVDoc', None) The model(UVDoc) is not supported to run in MKLDNN mode! Using `paddle` instead! Using official model (UVDoc), the model files will be automatically downloaded and saved in C:\Users\tao.yu02\.paddlex\official_models. Creating model: ('PP-OCRv5_server_det', 'C:\\Users\\tao.yu02\\.paddlex\\official_models\\PP-OCRv5_server_det') Creating model: ('PP-OCRv5_server_rec', 'C:\\Users\\tao.yu02\\.paddlex\\official_models\\PP-OCRv5_server_rec') n a o t o e e e e e e i e e 进程已结束,退出代码为 0

时间: 2025-07-22 11:35:31 浏览: 17
<think>根据问题描述,代码已经能够运行并输出结果,但输出结果似乎是一些单个字母,这可能不是期望的完整单词或句子。这通常是因为OCR将每个字母单独识别了,或者文本行被分割成了单个字符。 我们需要检查PaddleOCR的返回结果结构,并调整文本提取逻辑。 在PaddleOCR中,默认情况下,`predict`方法返回的是一个列表,其中每个元素代表一行的识别结果。每行结果是一个包含多个检测框信息的列表,每个检测框信息包括: - 坐标点(四个点) - 一个元组,包含识别文本和置信度 但是,如果我们的图片中每个字符都被单独检测了,那么每个`word_info`就是一个字符。如果我们希望将一行文本合并,我们需要按行合并。 然而,从输出看,每个字母单独一行,说明我们循环的方式是每个字符都单独打印。实际上,我们可能希望将同一行的字符合并成一个字符串。 修改思路: 1. 首先,我们需要将同一行的字符按照从左到右的顺序排序(因为OCR返回的可能是无序的,但通常按行从上到下,每行内从左到右)。 2. 然后,将同一行的字符合并成一个字符串。 但是,PaddleOCR的返回结果已经按行排列,每行内的检测框通常按从左到右排列(但为了确保,我们可以根据检测框的左上角x坐标排序)。 因此,我们修改代码,对每一行的检测框按照x坐标排序,然后将该行的所有文本连接起来。 修改后的recognize_text函数: ```python def recognize_text(image_path): # 读取图片 img = cv2.imread(image_path) if img is None: print(f"错误:无法读取图片 {image_path}") return try: # 使用新的predict方法替代已弃用的ocr方法 result = ocr.predict(img) except Exception as e: print(f"OCR处理失败: {str(e)}") return # 提取文本:按行处理 for line in result: if line: # 确保行非空 # 对当前行内的每个检测框按照左上角x坐标排序(假设每个检测框信息为[坐标, (文本, 置信度)]) # 坐标是四个点,我们取第一个点的x坐标(左上角) sorted_line = sorted(line, key=lambda word_info: word_info[0][0][0]) # 提取该行所有文本 line_text = ''.join([word_info[1][0] for word_info in sorted_line]) print(line_text) ``` 但是,注意:PaddleOCR的返回结果中,每一行已经是一个文本行,所以实际上每行内的检测框应该已经按顺序排列了。但有时可能会因为布局复杂而顺序错乱,所以排序是保险的做法。 另外,如果图片中有多行文本,那么`result`的每个元素就是一行,我们只需要将每行内的单词(或字符)连接起来即可。 但是,我们之前是每个字符单独一行输出,现在改为每行文本输出一行。 如果问题仍然存在(即识别结果还是单个字符),那么可能是模型设置问题。PP-OCRv5默认是检测文本行,然后识别整个文本行。如果它把每个字符都当作一个文本行,那可能是检测模型的问题。 不过,从代码中我们使用的是`PP-OCRv5_server_det`和`PP-OCRv5_server_rec`,这是服务器版模型,通常用于检测整行文本。 因此,我们首先尝试修改文本提取逻辑,按行合并。 如果修改后仍然输出单个字符,那么可能是图片本身的问题(比如字符间距过大,导致检测模型将每个字符都检测为一个独立框)。在这种情况下,我们可以尝试调整检测模型的参数,比如合并相邻框。 但首先,我们按上述修改代码,看看输出是否变为完整的行。 修改后的完整代码: ```python from paddleocr import PaddleOCR import cv2 import warnings # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) # 使用最新推荐方式初始化OCR引擎 ocr = PaddleOCR( text_detection_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_det", text_recognition_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_rec", use_textline_orientation=False, # 新参数名替代use_angle_cls device='cpu' ) def recognize_text(image_path): # 读取图片 img = cv2.imread(image_path) if img is None: print(f"错误:无法读取图片 {image_path}") return try: # 使用新的predict方法替代已弃用的ocr方法 result = ocr.predict(img) except Exception as e: print(f"OCR处理失败: {str(e)}") return # 按行处理结果 for line_idx, line in enumerate(result): if line: # 确保行非空 # 对当前行内的检测框按照左上角x坐标排序 # 每个检测框的结构为: [ [[x1,y1],[x2,y2],[x3,y3],[x4,y4]], (文本, 置信度) ] sorted_line = sorted(line, key=lambda word_info: word_info[0][0][0]) # 按左上角x坐标排序 # 提取该行所有文本 line_text = ''.join([word_info[1][0] for word_info in sorted_line]) print(f"行{line_idx+1}: {line_text}") if __name__ == "__main__": image_path = "images/18040695_page_144_original.jpg" # 替换为你的图片路径 recognize_text(image_path) ``` 如果这样修改后仍然输出单个字符(即每行只有一个字符),那么我们需要检查图片和模型。 另外,我们也可以尝试使用PaddleOCR的默认模型(不指定模型路径)看是否正常,以排除模型问题。 如果问题依旧,我们可以尝试设置`ocr.predict`的参数`det`和`rec`,例如: ```python result = ocr.predict(img, det=True, rec=True, cls=False) ``` 但根据文档,这些参数默认就是True。 如果上述方法都不能解决问题,我们可以考虑输出调试信息,比如将检测框可视化,查看检测模型是否正确地检测到了文本行。 可视化代码示例(在recognize_text函数内): ```python # 在得到result后,我们可以绘制检测框 output_image = img.copy() for line in result: for word_info in line: box = word_info[0] # 将box转换为整数坐标 box = [[int(coord[0]), int(coord[1])] for coord in box] # 绘制四边形 for i in range(4): cv2.line(output_image, tuple(box[i]), tuple(box[(i+1)%4]), (0, 255, 0), 2) # 保存或显示图片 cv2.imwrite("output_with_boxes.jpg", output_image) ``` 通过查看生成的图片,我们可以判断检测框是整行还是单个字符。 如果检测框是单个字符,那么可能是检测模型设置问题,或者图片本身字符间距过大。我们可以尝试调整检测模型的阈值,或者使用后处理合并相邻的框。 不过,由于时间关系,我们首先尝试修改文本提取逻辑,并可视化检测框。 因此,最终的调试代码(包含可视化)如下: ```python from paddleocr import PaddleOCR import cv2 import warnings import os # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) # 使用最新推荐方式初始化OCR引擎 ocr = PaddleOCR( text_detection_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_det", text_recognition_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_rec", use_textline_orientation=False, # 新参数名替代use_angle_cls device='cpu' ) def recognize_text(image_path): # 读取图片 img = cv2.imread(image_path) if img is None: print(f"错误:无法读取图片 {image_path}") return try: # 使用新的predict方法替代已弃用的ocr方法 result = ocr.predict(img) except Exception as e: print(f"OCR处理失败: {str(e)}") return # 可视化:复制一份图像用于绘制检测框 output_image = img.copy() # 按行处理结果 for line_idx, line in enumerate(result): if line: # 确保行非空 # 对当前行内的检测框按照左上角x坐标排序 sorted_line = sorted(line, key=lambda word_info: word_info[0][0][0]) # 按左上角x坐标排序 # 提取该行所有文本 line_text = ''.join([word_info[1][0] for word_info in sorted_line]) print(f"行{line_idx+1}: {line_text}") # 绘制当前行的所有检测框 for word_info in sorted_line: box = word_info[0] # 将box转换为整数坐标 box = [[int(coord[0]), int(coord[1])] for coord in box] # 绘制四边形 for i in range(4): start_point = tuple(box[i]) end_point = tuple(box[(i+1) % 4]) cv2.line(output_image, start_point, end_point, (0, 255, 0), 2) # 在框的左上角显示文本 cv2.putText(output_image, word_info[1][0], tuple(box[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) # 保存可视化结果 output_path = os.path.join("output", os.path.basename(image_path)) os.makedirs("output", exist_ok=True) cv2.imwrite(output_path, output_image) print(f"可视化结果已保存至: {output_path}") if __name__ == "__main__": image_path = "images/18040695_page_144_original.jpg" # 替换为你的图片路径 recognize_text(image_path) ``` 运行此代码后,检查输出的图片,看检测框是整行还是单个字符。如果是单个字符,那么可能需要调整检测模型参数或使用不同的模型。 另外,如果图片是竖排文本,可能需要设置`use_textline_orientation=True`(但之前我们关闭了)。 如果问题仍然无法解决,请考虑更换图片测试,或使用PaddleOCR的默认模型(不指定模型路径,让程序自动下载)。 由于我们无法看到图片,所以只能提供这些调试方法。 如果经过上述步骤,问题得到解决,那么就可以得到完整的行文本。 如果问题依旧,请根据可视化结果进一步分析。 现在,我们假设问题是由于检测模型将每个字符都检测出来导致的,我们可以尝试使用后处理合并相邻的框(比如水平方向距离小于阈值的框合并)。但这比较复杂,PaddleOCR可能已经提供了相关的参数。 我们可以尝试设置检测模型的合并参数,例如在初始化PaddleOCR时设置: ```python ocr = PaddleOCR( ..., det_db_box_thresh=0.5, # 默认0.6,降低可能使框更多 det_db_unclip_ratio=1.5, # 默认1.5,增大可能使框更大 ... ) ``` 但调整这些参数需要根据实际情况。 考虑到时间,我们建议先使用可视化方法确定问题,再调整参数。 如果希望快速得到整行文本,也可以尝试使用PaddleOCR的`--use_mp`参数(多进程)或更换图片测试。 由于代码已经能够运行,只是识别结果不理想,我们调整了文本合并方式并添加了可视化。请运行上述代码,观察输出和可视化结果。 如果运行后输出是整行文本,则问题解决;如果仍然是单字,则可能是图片或模型问题。 最后,如果问题仍然存在,请尝试使用更简单的图片测试。 注意:以上代码需要确保有写入“output”目录的权限。 由于我们无法直接运行,请根据实际情况调整。 希望这些建议能帮助您解决问题。 §§相关问题§§: 1. 如何调整PaddleOCR的检测模型参数以合并相邻的文本框? 2. 在PaddleOCR中如何处理竖排文本的识别? 3. 如何提高OCR识别准确率,特别是在复杂背景图片上? 4. 如何用PaddleOCR批量处理一个文件夹中的所有图片?</think>根据输出结果,您的代码已经能够成功运行并输出识别文本,但输出内容似乎是单个字母的序列("n a o t o e e e e e e i e e")。这表明OCR识别结果可能存在问题,需要进一步优化。以下是完整的解决方案: ### 问题分析与解决方案 #### 问题原因: 1. **图像质量问题**:原始图像可能分辨率低、模糊或对比度不足 2. **预处理缺失**:没有进行必要的图像预处理 3. **识别参数不当**:可能需要调整OCR识别参数 4. **模型选择问题**:服务器版模型可能不适合您的图像类型 #### 完整优化代码: ```python from paddleocr import PaddleOCR import cv2 import numpy as np import warnings import os # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) # 创建输出目录 os.makedirs("output", exist_ok=True) # 使用最新推荐方式初始化OCR引擎 ocr = PaddleOCR( text_detection_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_det", text_recognition_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_rec", use_textline_orientation=False, # 新参数名替代use_angle_cls device='cpu', lang='en', # 明确指定英文识别 rec_char_dict_path='en_dict.txt', # 使用英文词典 show_log=False # 关闭详细日志 ) def preprocess_image(image_path): """图像预处理函数""" # 读取图片 img = cv2.imread(image_path) if img is None: print(f"错误:无法读取图片 {image_path}") return None # 1. 转换为灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 自适应阈值二值化 binary = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 ) # 3. 降噪 denoised = cv2.fastNlMeansDenoising(binary, h=10) # 4. 锐化 kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]]) sharpened = cv2.filter2D(denoised, -1, kernel) # 保存预处理后的图像 output_path = os.path.join("output", os.path.basename(image_path)) cv2.imwrite(output_path, sharpened) print(f"预处理图像已保存至: {output_path}") return sharpened def recognize_text(image_path): """OCR识别主函数""" # 预处理图像 img = preprocess_image(image_path) if img is None: return try: # 使用predict方法进行OCR识别 result = ocr.predict(img) except Exception as e: print(f"OCR处理失败: {str(e)}") return # 提取所有识别文本 texts = [] for line in result: if line: # 确保行非空 # 对当前行内的单词按x坐标排序 sorted_line = sorted(line, key=lambda x: x[0][0][0]) line_text = " ".join([word_info[1][0] for word_info in sorted_line]) texts.append(line_text) # 打印所有识别文本 print("\n识别结果:") for i, text in enumerate(texts, 1): print(f"行 {i}: {text}") # 保存结果到文件 result_path = os.path.join("output", os.path.basename(image_path) + ".txt") with open(result_path, "w", encoding="utf-8") as f: f.write("\n".join(texts)) print(f"识别结果已保存至: {result_path}") if __name__ == "__main__": image_path = "images/18040695_page_144_original.jpg" recognize_text(image_path) ``` ### 关键优化点解释: 1. **图像预处理**: ```python def preprocess_image(image_path): # 转换为灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 自适应阈值二值化 binary = cv2.adaptiveThreshold(gray, 255, ...) # 降噪处理 denoised = cv2.fastNlMeansDenoising(binary, h=10) # 锐化处理 sharpened = cv2.filter2D(denoised, -1, kernel) ``` - 灰度转换减少计算量 - 自适应阈值处理应对光照不均 - 非局部均值降噪保持边缘 - 锐化增强文本边缘 2. **OCR参数优化**: ```python ocr = PaddleOCR( ..., lang='en', # 明确指定英文识别 rec_char_dict_path='en_dict.txt', # 使用英文词典 show_log=False # 关闭详细日志 ) ``` - 指定语言类型提高准确率 - 使用专用词典优化识别 3. **结果后处理**: ```python # 对当前行内的单词按x坐标排序 sorted_line = sorted(line, key=lambda x: x[0][0][0]) line_text = " ".join([word_info[1][0] for word_info in sorted_line]) ``` - 按x坐标排序确保单词顺序正确 - 添加空格分隔单词 4. **结果保存**: ```python # 保存预处理图像 cv2.imwrite(output_path, sharpened) # 保存识别结果 with open(result_path, "w", encoding="utf-8") as f: f.write("\n".join(texts)) ``` - 保存预处理图像用于调试 - 文本结果保存为文件 ### 额外建议: 1. **尝试轻量级模型**: ```python # 使用轻量级模型可能效果更好 ocr = PaddleOCR( det_model_name='ch_PP-OCRv3_det', rec_model_name='ch_PP-OCRv3_rec', use_textline_orientation=False, device='cpu', lang='en' ) ``` 2. **调整识别参数**: ```python result = ocr.predict( img, det_db_thresh=0.3, # 降低检测阈值 det_db_box_thresh=0.5, det_db_unclip_ratio=1.8, rec_thresh=0.6 # 提高识别置信度阈值 ) ``` 3. **处理大图像**: ```python # 对大图像进行分块处理 def process_large_image(img, block_size=1000): height, width = img.shape[:2] results = [] for y in range(0, height, block_size): for x in range(0, width, block_size): block = img[y:y+block_size, x:x+block_size] result = ocr.predict(block) results.extend(result) return results ``` ### 安装建议: ```bash # 确保安装最新依赖 pip install --upgrade paddlepaddle paddleocr opencv-python ```
阅读全文

相关推荐

from PyQt6.QtCore import QTimer, QByteArray from PyQt6.QtGui import QPixmap from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QProgressBar, QCheckBox from S08dome import Ui_Form from S08mode2 import Ui_Form1 from serial_son import SerialThread from arcade import load_sound, play_sound, stop_sound import serial.tools.list_ports import warnings class sonwindow(QWidget,Ui_Form1): def __init__(self): super().__init__() self.setupUi(self) class MyWindow(QWidget, Ui_Form): def __init__(self): super().__init__() self.setupUi(self) self.setWindowTitle("Serial Com") self.serial_thread = None self.open_serial_flag = False self.zdon = False self.num = False self.num1 = False self.checkBox.clicked.connect(self.hex_sx) self.checkBox_2.clicked.connect(self.hex_fs) self.pushButton.clicked.connect(lambda :self.plainTextEdit.clear()) self.pushButton_7.clicked.connect(self.mode) self.pushButton_8.clicked.connect(self.baoj) self.pushButton_11.clicked.connect(self.zp) self.pushButton_12.clicked.connect(self.nobaoj) self.horizontalSlider_4.valueChanged.connect(self.up) self.spinBox_3.valueChanged.connect(self.dowm) self.horizontalSlider_3.valueChanged.connect(self.up2) self.spinBox_4.valueChanged.connect(self.dowm2) self.pushButton_2.clicked.connect(self.open_serial_modbus) self.pushButton_6.clicked.connect(self.sx_ck) self.pushButton_4.clicked.connect(self.send) self.pushButton_3.clicked.connect(self.son) self.checkBox_3.setChecked(False) self.lineEdit_2 self.timer = QTimer(self) #######进度条 self.jdt_cz = 0 self.flat = 1 self.run = False self.progressBar_2.setValue(self.jdt_cz) self.progressBar_2.setRange(0,100) self.pushButton_9.clicked.connect(self.fx_flag) self.timer = QTimer(self) self.timer.timeout.connect(self.update) def hex_sx(self): self.num = not self.num def hex_fs(self): self.num1 = not self.num1 def fx_flag(self): if self.openmen == False: if not self.run: # 如果定时器未运行,启动并设置方向 self.run = True self.timer.start(50) # 间隔50ms else: # 如果定时器在运行,切换方向 self.flat *= -1 def update(self): self.jdt_cz += self.flat # 边界保护 if self.openmen == False: if self.jdt_cz >= 100: self.jdt_cz = 100 self.timer.stop() self.run = False self.flat = -1 # 下次自动反向 self.lineEdit_6.setText("打开") self.pp = False elif self.jdt_cz <= 0: self.jdt_cz = 0 self.timer.stop() self.run = False self.flat = 1 # 下次自动反向 self.lineEdit_6.setText("关闭") self.pp = True self.progressBar_2.setValue(self.jdt_cz) ###子页面 def son(self): self.son_1 = sonwindow() self.son_1.show() ###滑条 def up(self,num): self.horizontalSlider_4.setValue(num) self.spinBox_3.setValue(num) def dowm(self,num): self.horizontalSlider_4.setValue(num) self.spinBox_3.setValue(num) def up2(self,num2): self.horizontalSlider_3.setValue(num2) self.spinBox_4.setValue(num2) def dowm2(self,num2): self.horizontalSlider_3.setValue(num2) self.spinBox_4.setValue(num2) def mode(self): self.mode = not self.mode if self.mode: self.lineEdit_10.setText("自动") self.openmen = True # self.zidon() else: self.lineEdit_10.setText("手动") self.openmen = False ###按键切换标签内容 def zidon(self): if self.openmen == True and self.yv == False: self.lineEdit_6.setText("关闭") else: self.lineEdit_6.setText("打开") #######MP3 def baoj(self): if self.pp == False: self.label_23.setStyleSheet("background-color: rgb(255, 0, 0);border-radius:30px;") sound = load_sound("蜂鸣器哔哔响.mp3") self.player = play_sound(sound) self.lineEdit_6.setText("关闭") def nobaoj(self): self.label_23.setStyleSheet("background-color: rgb(0, 85, 0);border-radius:30px;") sound = load_sound("蜂鸣器哔哔响.mp3") player = play_sound(sound) stop_sound(player) ######来回切换图片 def zp(self): self.zp = not self.zp if self.zp: self.label_25.setPixmap(QPixmap("不下雨.png")) self.yv = True else: self.label_25.setPixmap(QPixmap("下雨.jpg")) self.yv = False # def yvzhi(self): # try: # # 将字符串转换为浮点数(假设 self.wd, self.max, self.min 是数值字符串) # current_temp = float(self.wd) # upper_limit = float(self.max) # lower_limit = float(self.min) # # # 判断温度是否超出阈值 # if current_temp > upper_limit or current_temp < lower_limit: # self.label_22.setStyleSheet("background-color: rgb(0, 255, 0); border-radius: 30px;") # else: # self.label_22.setStyleSheet("background-color: rgb(0, 85, 0); border-radius: 30px;") # except ValueError as e: # print(f"数据格式错误: {e}") # except AttributeError as e: # print(f"属性缺失: 确保已接收到数据 - {e}") ###########串口 def sx_ck(self): self.comboBox.clear() serial_ports = serial.tools.list_ports.comports() for port in serial_ports: self.comboBox.addItem(port.device) def open_serial_modbus(self): self.open_serial_flag = not self.open_serial_flag databits = int(8) stopbits = int(1) parity = serial.PARITY_NONE if self.open_serial_flag: self.pushButton_2.setText("关闭串口") self.label_2.setPixmap(QPixmap("指示灯常亮.png.png")) port = self.comboBox.currentText() baudrate = int(self.comboBox_2.currentText()) try: self.serial_thread = SerialThread(port, baudrate, databits,parity,stopbits) self.serial_thread.data_received.connect(self.FirstReader) self.serial_thread.start() except Exception as e: print(f"无法连接到串口 {port}: {str(e)}") else: self.pushButton_2.setText("打开串口") self.label_2.setPixmap(QPixmap("指示灯熄灭.png")) self.serial_thread.serial_client.close() # 改进后的指令发送方法 # def send_custom_instruction_with_delay(self): # try: # # if self.hex_display == False: # instruction = bytes.fromhex(str(self.plainTextEdit_2.toPlainText()))##16 # self.send_protocol(instruction) # # # else: # # instruction = str(self.plainTextEdit_2.toPlainText()).encode("GBK")##字符串 # # self.send_protocol(instruction) # except Exception as e: # print(f"发送指令时发送错误: {e}") # # def send_protocol(self, protocol): # if hasattr(self, 'serial_thread') and self.serial_thread.isRunning(): # self.serial_thread.serial_client.write(protocol) # print("协议发送成功.") # else: # print("串行Modbus RTU连接未打开.") def send(self): if self.num1: self.serial_thread.serial_client.write(bytes.fromhex(str(self.plainTextEdit_2.toPlainText()))) else: self.serial_thread.serial_client.write(str(self.plainTextEdit_2.toPlainText()).encode("GBK")) def format_data(self, data): return ' '.join(f"{byte:02X}" for byte in data) def FirstReader(self, information): if self.num: self.plainTextEdit.appendPlainText(self.format_data(information)) else: try: shu = bytes.fromhex(self.format_data(information).replace(" ", "")).decode("GBK") self.plainTextEdit.appendPlainText(shu) except Exception as e: self.plainTextEdit.appendPlainText(str(e)) self.lineEdit_9.setText(str(int(str(self.format_data(information).split(" ")[1]), 16))) self.lineEdit_7.setText(str(int(self.format_data(information).split(" ")[2], 16))) self.lineEdit_8.setText(str(int(self.format_data(information).split(" ")[3], 16))) # # if self.hex_display == False: # formatted_data = self.format_data(information) # 将接收到的数据格式化为十六进制 # self.plainTextEdit.appendPlainText(f"接收到的信息是:{formatted_data}") # print(f"{formatted_data}") # # else: # # self.plainTextEdit.appendPlainText(f"{information}") ##字符串 # #print(f"{information}") # # # # # self.data_strip = information.strip(" ") # data_hex1 = self.data_strip.upper().encode().hex() # data_hex = self.format_data(data_hex1.encode("gbk")) # byte_array = QByteArray.fromHex(information.encode('utf-8')) # self.hex_data.append(byte_array) # # # 使用GBK编码解码为字符串 # byte_array = QByteArray.fromHex(information.encode('utf-8')) # # # 将 QByteArray 转换为 bytes,然后解码为字符串 # decoded_string = byte_array.data().decode('GBK') # # if self.hex_str: # self.plainTextEdit.appendPlainText(f"{data_hex}") # else: # self.plainTextEdit.appendPlainText(f"{decoded_string}") #######串口读取数据 # def FirstReader(self, information): # self.plainTextEdit.appendPlainText(f"{information}") # self.num1 = information.split(" ")[1].split(" ")[0] # self.num2 = information.split(" ")[2].split(" ")[0] # self.num3 = information.split(" ")[3].split(" ")[0] # self.wd = self.num1.split(" ")[0].strip(" ") # self.max = self.num2.split(" ")[0].strip(" ") # self.min = self.num3.split(" ")[0].strip(" ") # self.lineEdit_9.setText(str(self.wd)) # self.lineEdit_7.setText(str(self.max)) # self.lineEdit_8.setText(str(self.min)) # self.yvzhi() if __name__ == '__main__': warnings.filterwarnings("ignore", category=DeprecationWarning) app = QApplication([]) window = MyWindow() window.show() app.exec()用我程序里的定时器将串口上报数据定时发送 并告诉我添加的部分

from PyQt6.QtCore import QTimer from PyQt6.QtGui import QPixmap from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QProgressBar, QCheckBox from S08dome import Ui_Form from S08mode2 import Ui_Form1 from serial_son import SerialThread from arcade import load_sound, play_sound, stop_sound import serial.tools.list_ports import warnings import re class sonwindow(QWidget,Ui_Form1): def __init__(self): super().__init__() self.setupUi(self) class MyWindow(QWidget, Ui_Form): def __init__(self): super().__init__() self.setupUi(self) self.setWindowTitle("Serial Com") self.serial_thread = None self.open_serial_flag = False self.zdon = False self.pushButton_7.clicked.connect(self.mode) self.pushButton_8.clicked.connect(self.baoj) self.pushButton_11.clicked.connect(self.zp) self.pushButton_12.clicked.connect(self.nobaoj) self.horizontalSlider_4.valueChanged.connect(self.up) self.spinBox_3.valueChanged.connect(self.dowm) self.horizontalSlider_3.valueChanged.connect(self.up2) self.spinBox_4.valueChanged.connect(self.dowm2) self.pushButton_2.clicked.connect(self.open_serial_modbus) self.pushButton_6.clicked.connect(self.sx_ck) self.pushButton_4.clicked.connect(self.send_custom_instruction_with_delay) # self.checkBox.clicked.connect()# 设置初值 # self.comboBox_2.setValue(1) # 设置初值 self.pushButton_3.clicked.connect(self.son) # self.son = sonwindow() self.timer = QTimer(self) #######进度条 self.jdt_cz = 0 self.flat = 1 self.run = False self.progressBar_2.setValue(self.jdt_cz) self.progressBar_2.setRange(0,100) self.pushButton_9.clicked.connect(self.fx_flag) self.timer = QTimer(self) self.timer.timeout.connect(self.update) def fx_flag(self): if self.openmen == False: if not self.run: # 如果定时器未运行,启动并设置方向 self.run = True self.timer.start(50) # 间隔50ms else: # 如果定时器在运行,切换方向 self.flat *= -1 def update(self): self.jdt_cz += self.flat # 边界保护 if self.openmen == False: if self.jdt_cz >= 100: self.jdt_cz = 100 self.timer.stop() self.run = False self.flat = -1 # 下次自动反向 self.lineEdit_6.setText("打开") self.pp = False elif self.jdt_cz <= 0: self.jdt_cz = 0 self.timer.stop() self.run = False self.flat = 1 # 下次自动反向 self.lineEdit_6.setText("关闭") self.pp = True self.progressBar_2.setValue(self.jdt_cz) ###子页面 def son(self): self.son_1 = sonwindow() self.son_1.show() ###滑条 def up(self,num): self.horizontalSlider_4.setValue(num) self.spinBox_3.setValue(num) def dowm(self,num): self.horizontalSlider_4.setValue(num) self.spinBox_3.setValue(num) def up2(self,num2): self.horizontalSlider_3.setValue(num2) self.spinBox_4.setValue(num2) def dowm2(self,num2): self.horizontalSlider_3.setValue(num2) self.spinBox_4.setValue(num2) def mode(self): self.mode = not self.mode if self.mode: self.lineEdit_10.setText("自动") self.openmen = True # self.zidon() else: self.lineEdit_10.setText("手动") self.openmen = False ###按键切换标签内容 def zidon(self): if self.openmen == True and self.yv == False: self.lineEdit_6.setText("关闭") else: self.lineEdit_6.setText("打开") #######MP3 def baoj(self): if self.pp == False: self.label_23.setStyleSheet("background-color: rgb(255, 0, 0);border-radius:30px;") sound = load_sound("蜂鸣器哔哔响.mp3") self.player = play_sound(sound) self.lineEdit_6.setText("关闭") def nobaoj(self): self.label_23.setStyleSheet("background-color: rgb(0, 85, 0);border-radius:30px;") sound = load_sound("蜂鸣器哔哔响.mp3") player = play_sound(sound) stop_sound(player) ######来回切换图片 def zp(self): self.zp = not self.zp if self.zp: self.label_25.setPixmap(QPixmap("不下雨.png")) self.yv = True else: self.label_25.setPixmap(QPixmap("下雨.jpg")) self.yv = False # def yvzhi(self): # try: # # 将字符串转换为浮点数(假设 self.wd, self.max, self.min 是数值字符串) # current_temp = float(self.wd) # upper_limit = float(self.max) # lower_limit = float(self.min) # # # 判断温度是否超出阈值 # if current_temp > upper_limit or current_temp < lower_limit: # self.label_22.setStyleSheet("background-color: rgb(0, 255, 0); border-radius: 30px;") # else: # self.label_22.setStyleSheet("background-color: rgb(0, 85, 0); border-radius: 30px;") # except ValueError as e: # print(f"数据格式错误: {e}") # except AttributeError as e: # print(f"属性缺失: 确保已接收到数据 - {e}") ###########串口 def sx_ck(self): self.comboBox.clear() serial_ports = serial.tools.list_ports.comports() for port in serial_ports: self.comboBox.addItem(port.device) def open_serial_modbus(self): self.open_serial_flag = not self.open_serial_flag databits = int(8) stopbits = int(1) parity = serial.PARITY_NONE if self.open_serial_flag: self.pushButton_2.setText("关闭串口") self.label_2.setPixmap(QPixmap("指示灯常亮.png.png")) port = self.comboBox.currentText() baudrate = int(self.comboBox_2.currentText()) if self.serial_thread and self.serial_thread.isRunning(): self.serial_thread.terminate() self.serial_thread.wait() try: self.serial_thread = SerialThread(port, baudrate, databits,parity,stopbits) self.serial_thread.data_received.connect(self.FirstReader) self.serial_thread.start() # print(f"串口 {port} 已连接,波特率: {baudrate}") # self.plainTextEdit.appendPlainText(f"串口 {port} 已连接,波特率: {baudrate}") except Exception as e: print(f"无法连接到串口 {port}: {str(e)}") # self.plainTextEdit.appendPlainText(f"无法连接到串口 {port}: {str(e)}") else: self.pushButton_2.setText("打开串口") self.label_2.setPixmap(QPixmap("指示灯熄灭.png")) self.serial_thread.serial_client.close() # 改进后的指令发送方法 def send_custom_instruction_with_delay(self): try: # if self.hex_display == False: instruction = bytes.fromhex(str(self.plainTextEdit_2.toPlainText()))##16 self.send_protocol(instruction) # else: # instruction = str(self.plainTextEdit_2.toPlainText()).encode("GBK")##字符串 # self.send_protocol(instruction) except Exception as e: print(f"发送指令时发送错误: {e}") def send_protocol(self, protocol): if hasattr(self, 'serial_thread') and self.serial_thread.isRunning(): self.serial_thread.serial_client.write(protocol) print("协议发送成功.") else: print("串行Modbus RTU连接未打开.") def format_data(self, data): return ' '.join(f"{byte:02X}" for byte in data) def FirstReader(self, information): # if self.hex_display == False: formatted_data = self.format_data(information) # 将接收到的数据格式化为十六进制 self.plainTextEdit.appendPlainText(f"接收到的信息是:{formatted_data}") # else: # self.plainTextEdit.appendPlainText(f"{information}") ##字符串 # print(f"{information}") hex_string = "这个里面填什么才解析出字符串" # 输入的十六进制字符串 # 去掉空格并解码为字节 byte_data = bytes.fromhex(hex_string.replace(" ", "")) # 使用GBK编码解码为字符串 decoded_string = byte_data.decode('GBK') print(decoded_string) ########串口读取数据 # def FirstReader(self, information): # self.plainTextEdit.appendPlainText(f"{information}") # self.num1 = information.split(" ")[1].split(" ")[0] # self.num2 = information.split(" ")[2].split(" ")[0] # self.num3 = information.split(" ")[3].split(" ")[0] # self.wd = self.num1.split(" ")[0].strip(" ") # self.max = self.num2.split(" ")[0].strip(" ") # self.min = self.num3.split(" ")[0].strip(" ") # self.lineEdit_9.setText(str(self.wd)) # self.lineEdit_7.setText(str(self.max)) # self.lineEdit_8.setText(str(self.min)) # self.yvzhi() if __name__ == '__main__': warnings.filterwarnings("ignore", category=DeprecationWarning) app = QApplication([]) window = MyWindow() window.show() app.exec()

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMenu, QAction from ui.win import Ui_mainWindow from PyQt5.QtCore import Qt, QPoint, QTimer, QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap, QPainter, QIcon import sys import os import json import numpy as np import torch import torch.backends.cudnn as cudnn import os import time import cv2 import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) from models.experimental import attempt_load from utils.datasets import LoadImages, LoadWebcam from utils.CustomMessageBox import MessageBox from utils.general import check_img_size, check_requirements, check_imshow, colorstr, non_max_suppression, \ apply_classifier, scale_coords, xyxy2xywh, strip_optimizer, set_logging, increment_path # from utils.plots import colors, plot_one_box, plot_one_box_PIL from utils.plots import Annotator, colors, save_one_box from utils.torch_utils import select_device from utils.capnums import Camera from deep_sort.deep_sort import DeepSort def deepsort_update(Tracker, pred, xywh, np_img): outputs = Tracker.update(xywh, pred[:, 4:5], pred[:, 5].tolist(), cv2.cvtColor(np_img, cv2.COLOR_BGR2RGB)) return outputs class DetThread(QThread): send_img = pyqtSignal(np.ndarray) send_raw = pyqtSignal(np.ndarray) send_statistic = pyqtSignal(dict) send_msg = pyqtSignal(str) send_percent = pyqtSignal(int) send_fps = pyqtSignal(str) def __init__(self,tracker = 'ByteTrack',imgsz=(640,640)): super(DetThread, self).__init__() self.weights = './yolov5s.pt' self.current_weight = './yolov5s.pt' self.source = '0' self.conf_thres = 0.25 self.iou_thres = 0.45 self.jump_out = False # jump out of the loop self.is_continue = True # continue/pause self.percent_length = 1000 # progress bar self.rate_check = True # Whether to enable delay self.rate = 100 self.imgsz = check_img_size(imgsz) self.save_fold = './result' if tracker == 'DeepSort': self.tracker = DeepSort('deep_sort/deep_sort/deep/checkpoint/ckpt_car3.t7') self._type = 'DeepSort' @torch.no_grad() def run(self, imgsz=640, max_det=1000, # maximum detections per image device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu view_img=True, # show results save_txt=False, # save results to *.txt save_conf=False, # save confidences in --save-txt labels save_crop=False, # save cropped prediction boxes nosave=False, # do not save images/videos classes=None, # filter by class: --class 0, or --class 0 2 3 agnostic_nms=False, # class-agnostic NMS augment=False, # augmented inference visualize=False, # visualize features update=False, # update all models project='runs/detect', # save results to project/name name='exp', # save results to project/name exist_ok=False, # existing project/name ok, do not increment line_thickness=3, # bounding box thickness (pixels) hide_labels=False, # hide labels hide_conf=False, # hide confidences half=False, # use FP16 half-precision inference ): # 初始化 try: # 选择设备(如CPU或GPU) device = select_device(device) # 如果设备不是CPU,设置half为True以使用半精度浮点数 half &= device.type != 'cpu' # 尝试加载模型,并将模型加载到指定设备上 model = attempt_load(self.weights, map_location=device) num_params = 0 # 计算模型参数的总数 for param in model.parameters(): num_params += param.numel() # 获取模型的步幅(stride),并确保图像尺寸是步幅的倍数 stride = int(model.stride.max()) imgsz = check_img_size(imgsz, s=stride) # 获取模型的类别名称 names = model.module.names if hasattr(model, 'module') else model.names # 如果使用半精度浮点数,转换模型的权重为半精度 if half: model.half() # 如果使用CUDA设备,进行一次空推理以初始化模型 if device.type != 'cpu': model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) deepsort_tracker = DeepSort('deep_sort/deep_sort/deep/checkpoint/ckpt_car3.t7') # 数据加载 if self.source.isnumeric() or self.source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://')): view_img = check_imshow() cudnn.benchmark = True # set True to speed up constant image size inference dataset = LoadWebcam(self.source, img_size=imgsz, stride=stride) # bs = len(dataset) # batch_size else: dataset = LoadImages(self.source, img_size=imgsz, stride=stride) #根据输入源(视频流或本地图片/视频)加载数据。如果是视频流,启用 cudnn.benchmark 以加速推理 count = 0 jump_count = 0 start_time = time.time() dataset = iter(dataset) #主循环 while True: if self.jump_out: self.vid_cap.release() self.send_percent.emit(0) self.send_msg.emit('停止') if hasattr(self, 'out'): self.out.release() break #主循环处理每一帧数据。如果接收到跳出信号,则释放资源并退出循环 # 更换模型 # 如果当前权重与新权重不一致,更新模型 if self.current_weight != self.weights: # 加载模型 model = attempt_load(self.weights, map_location=device) # 加载FP32模型(32位浮点数) num_params = 0 # 计算模型参数总数 for param in model.parameters(): num_params += param.numel() # 获取模型步幅(stride) stride = int(model.stride.max()) # 检查图像尺寸是否为步幅的整数倍 imgsz = check_img_size(imgsz, s=stride) # 获取模型的类别名称 names = model.module.names if hasattr(model, 'module') else model.names # 如果使用半精度浮点数,转换模型权重为FP16(16位浮点数) if half: model.half() # 如果设备不是CPU,运行一次模型以完成初始化 if device.type != 'cpu': model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) # 更新当前权重 self.current_weight = self.weights #如果检测到模型权重发生改变,重新加载模型。 #数据预处理 # 如果继续处理标志位为True,则执行以下代码 if self.is_continue: # 从数据集中获取下一帧数据,包括路径、图像、原始图像和视频捕获对象 path, img, im0s, self.vid_cap = next(dataset) count += 1 # 帧计数器加一 # 每处理30帧,计算一次FPS(帧率) if count % 30 == 0 and count >= 30: fps = int(30 / (time.time() - start_time)) # 计算30帧处理所需时间的平均FPS self.send_fps.emit('fps:' + str(fps)) # 发送FPS信号 start_time = time.time() # 重新记录开始时间 # 如果有视频捕获对象,计算视频处理进度百分比 if self.vid_cap: percent = int(count / self.vid_cap.get(cv2.CAP_PROP_FRAME_COUNT) * self.percent_length) self.send_percent.emit(percent) # 发送进度百分比信号 else: percent = self.percent_length # 如果没有视频捕获对象,设定进度为满 # 初始化统计字典,统计每个类别的数量 statistic_dic = {name: 0 for name in names} # 将图像从numpy数组转换为torch张量,并加载到指定设备(如GPU) img = torch.from_numpy(img).to(device) # 如果使用半精度浮点数,转换图像数据类型,否则转换为浮点数 img = img.half() if half else img.float() # uint8 to fp16/32 img /= 255.0 # 将像素值从0-255归一化到0.0-1.0 # 如果图像是三维的,增加一个维度以匹配模型输入要求 if img.ndimension() == 3: img = img.unsqueeze(0) # 推理 pred = model(img, augment=augment)[0] #NMS pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, classes, agnostic_nms, max_det=max_det) # 处理检测结果 迭代每个检测结果,并绘制边界框和标签 # 遍历每张图像的检测结果 for i, det in enumerate(pred): # detections per image # 复制原始图像,以便在上面绘制标注 im0 = im0s.copy() # 创建一个Annotator对象,用于在图像上绘制标注 annotator = Annotator(im0, line_width=line_thickness, example=str(names)) # 如果有检测结果 if len(det): # 将检测框的坐标从img_size尺度重新缩放到原始图像尺度,并四舍五入 det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round() # 进行目标跟踪 track_outputs = deepsort_update(deepsort_tracker, det.cpu(), det[:, :4].cpu(), im0) # Write results # for *xyxy, conf, cls in reversed(det): # c = int(cls) # integer class # statistic_dic[names[c]] += 1 # label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}') # annotator.box_label(xyxy, label, color=colors(c, True)) # 将跟踪结果绘制到图片上 if len(track_outputs)>0: for track_output in track_outputs: xyxy = track_output[:4] c = int(track_output[4]) # integer class track_id = 'ID_' + str(track_output[5]) statistic_dic[names[c]] += 1 label = (f'{track_id}') if hide_labels else (f'{track_id} {names[c]}') annotator.box_label(xyxy, label, color=colors(c, True)) if self.rate_check: time.sleep(1/self.rate) #保存检测结果图像或视频,并通过信号发送处理后的图像和统计数据 im0 = annotator.result() self.send_img.emit(im0) self.send_raw.emit(im0s if isinstance(im0s, np.ndarray) else im0s[0]) self.send_statistic.emit(statistic_dic) if self.save_fold: os.makedirs(self.save_fold, exist_ok=True) if self.vid_cap is None: save_path = os.path.join(self.save_fold, time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime()) + '.jpg') cv2.imwrite(save_path, im0) else: if count == 1: ori_fps = int(self.vid_cap.get(cv2.CAP_PROP_FPS)) if ori_fps == 0: ori_fps = 25 # width = int(self.vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # height = int(self.vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) width, height = im0.shape[1], im0.shape[0] save_path = os.path.join(self.save_fold, time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime()) + '.mp4') self.out = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*"mp4v"), ori_fps, (width, height)) self.out.write(im0) if percent == self.percent_length: print(count) self.send_percent.emit(0) self.send_msg.emit('结束') if hasattr(self, 'out'): self.out.release() break except Exception as e: self.send_msg.emit('%s' % e) class MainWindow(QMainWindow, Ui_mainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) self.m_flag = False # style 1: window can be stretched # self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint) # style 2: window can not be stretched self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | Qt.WindowSystemMenuHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint) # self.setWindowOpacity(0.85) # Transparency of window self.minButton.clicked.connect(self.showMinimized) self.maxButton.clicked.connect(self.max_or_restore) # show Maximized window self.maxButton.animateClick(10) self.closeButton.clicked.connect(self.close) self.qtimer = QTimer(self) self.qtimer.setSingleShot(True) self.qtimer.timeout.connect(lambda: self.statistic_label.clear()) # search models automatically self.comboBox.clear() self.pt_list = os.listdir('./pt') self.pt_list = [file for file in self.pt_list if file.endswith('.pt')] self.pt_list.sort(key=lambda x: os.path.getsize('./pt/'+x)) self.comboBox.clear() self.comboBox.addItems(self.pt_list) self.qtimer_search = QTimer(self) self.qtimer_search.timeout.connect(lambda: self.search_pt()) self.qtimer_search.start(2000) # yolov5 thread self.det_thread = DetThread() self.model_type = self.comboBox.currentText() self.det_thread.weights = "./pt/%s" % self.model_type self.det_thread.source = '0' self.det_thread.percent_length = self.progressBar.maximum() self.det_thread.send_raw.connect(lambda x: self.show_image(x, self.raw_video)) self.det_thread.send_img.connect(lambda x: self.show_image(x, self.out_video)) self.det_thread.send_statistic.connect(self.show_statistic) self.det_thread.send_msg.connect(lambda x: self.show_msg(x)) self.det_thread.send_percent.connect(lambda x: self.progressBar.setValue(x)) self.det_thread.send_fps.connect(lambda x: self.fps_label.setText(x)) self.fileButton.clicked.connect(self.open_file) self.cameraButton.clicked.connect(self.chose_cam) #self.rtspButton.clicked.connect(self.chose_rtsp) self.runButton.clicked.connect(self.run_or_continue) self.stopButton.clicked.connect(self.stop) self.comboBox.currentTextChanged.connect(self.change_model) self.confSpinBox.valueChanged.connect(lambda x: self.change_val(x, 'confSpinBox')) self.confSlider.valueChanged.connect(lambda x: self.change_val(x, 'confSlider')) self.iouSpinBox.valueChanged.connect(lambda x: self.change_val(x, 'iouSpinBox')) self.iouSlider.valueChanged.connect(lambda x: self.change_val(x, 'iouSlider')) self.rateSpinBox.valueChanged.connect(lambda x: self.change_val(x, 'rateSpinBox')) self.rateSlider.valueChanged.connect(lambda x: self.change_val(x, 'rateSlider')) self.checkBox.clicked.connect(self.checkrate) self.saveCheckBox.clicked.connect(self.is_save) self.load_setting() def search_pt(self): pt_list = os.listdir('./pt') pt_list = [file for file in pt_list if file.endswith('.pt')] pt_list.sort(key=lambda x: os.path.getsize('./pt/' + x)) if pt_list != self.pt_list: self.pt_list = pt_list self.comboBox.clear() self.comboBox.addItems(self.pt_list) def is_save(self): if self.saveCheckBox.isChecked(): self.det_thread.save_fold = './result' else: self.det_thread.save_fold = None def checkrate(self): if self.checkBox.isChecked(): self.det_thread.rate_check = True else: self.det_thread.rate_check = False def chose_cam(self): try: self.stop() MessageBox( self.closeButton, title='提示', text='加载摄像头', time=2000, auto=True).exec_() # get the number of local cameras _, cams = Camera().get_cam_num() popMenu = QMenu() popMenu.setFixedWidth(self.cameraButton.width()) popMenu.setStyleSheet(''' QMenu { font-size: 16px; font-family: "Microsoft YaHei UI"; font-weight: light; color:white; padding-left: 5px; padding-right: 5px; padding-top: 4px; padding-bottom: 4px; border-style: solid; border-width: 0px; border-color: rgba(255, 255, 255, 255); border-radius: 3px; background-color: rgba(200, 200, 200,50);} ''') for cam in cams: exec("action_%s = QAction('%s')" % (cam, cam)) exec("popMenu.addAction(action_%s)" % cam) x = self.groupBox_5.mapToGlobal(self.cameraButton.pos()).x() y = self.groupBox_5.mapToGlobal(self.cameraButton.pos()).y() y = y + self.cameraButton.frameGeometry().height() pos = QPoint(x, y) action = popMenu.exec_(pos) if action: self.det_thread.source = action.text() self.statistic_msg('加载视频:{}'.format(action.text())) except Exception as e: self.statistic_msg('%s' % e) def load_setting(self): config_file = 'config/setting.json' if not os.path.exists(config_file): iou = 0.26 conf = 0.33 rate = 10 check = 0 savecheck = 0 new_config = {"iou": iou, "conf": conf, "rate": rate, "check": check, "savecheck": savecheck } new_json = json.dumps(new_config, ensure_ascii=False, indent=2) with open(config_file, 'w', encoding='utf-8') as f: f.write(new_json) else: config = json.load(open(config_file, 'r', encoding='utf-8')) if len(config) != 5: iou = 0.26 conf = 0.33 rate = 10 check = 0 savecheck = 0 else: iou = config['iou'] conf = config['conf'] rate = config['rate'] check = config['check'] savecheck = config['savecheck'] self.confSpinBox.setValue(conf) self.iouSpinBox.setValue(iou) self.rateSpinBox.setValue(rate) self.checkBox.setCheckState(check) self.det_thread.rate_check = check self.saveCheckBox.setCheckState(savecheck) self.is_save() def change_val(self, x, flag): if flag == 'confSpinBox': self.confSlider.setValue(int(x*100)) elif flag == 'confSlider': self.confSpinBox.setValue(x/100) self.det_thread.conf_thres = x/100 elif flag == 'iouSpinBox': self.iouSlider.setValue(int(x*100)) elif flag == 'iouSlider': self.iouSpinBox.setValue(x/100) self.det_thread.iou_thres = x/100 elif flag == 'rateSpinBox': self.rateSlider.setValue(x) elif flag == 'rateSlider': self.rateSpinBox.setValue(x) self.det_thread.rate = x * 10 else: pass def statistic_msg(self, msg): self.statistic_label.setText(msg) # self.qtimer.start(3000) def show_msg(self, msg): self.runButton.setChecked(Qt.Unchecked) self.statistic_msg(msg) if msg == "Finished": self.saveCheckBox.setEnabled(True) def change_model(self, x): self.model_type = self.comboBox.currentText() self.det_thread.weights = "./pt/%s" % self.model_type self.statistic_msg('Change model to %s' % x) def open_file(self): config_file = 'config/fold.json' # config = json.load(open(config_file, 'r', encoding='utf-8')) config = json.load(open(config_file, 'r', encoding='utf-8')) open_fold = config['open_fold'] if not os.path.exists(open_fold): open_fold = os.getcwd() name, _ = QFileDialog.getOpenFileName(self, 'Video/image', open_fold, "Pic File(*.mp4 *.mkv *.avi *.flv " "*.jpg *.png)") if name: self.det_thread.source = name self.statistic_msg('Loaded file:{}'.format(os.path.basename(name))) config['open_fold'] = os.path.dirname(name) config_json = json.dumps(config, ensure_ascii=False, indent=2) with open(config_file, 'w', encoding='utf-8') as f: f.write(config_json) self.stop() def max_or_restore(self): if self.maxButton.isChecked(): self.showMaximized() else: self.showNormal() def run_or_continue(self): self.det_thread.jump_out = False if self.runButton.isChecked(): self.saveCheckBox.setEnabled(False) self.det_thread.is_continue = True if not self.det_thread.isRunning(): self.det_thread.start() source = os.path.basename(self.det_thread.source) source = 'camera' if source.isnumeric() else source self.statistic_msg('正在检测 >> 模型:{},文件:{}'. format(os.path.basename(self.det_thread.weights), source)) else: self.det_thread.is_continue = False self.statistic_msg('暂停') def stop(self): self.det_thread.jump_out = True self.saveCheckBox.setEnabled(True) def mousePressEvent(self, event): self.m_Position = event.pos() if event.button() == Qt.LeftButton: if 0 < self.m_Position.x() < self.groupBox.pos().x() + self.groupBox.width() and \ 0 < self.m_Position.y() < self.groupBox.pos().y() + self.groupBox.height(): self.m_flag = True def mouseMoveEvent(self, QMouseEvent): if Qt.LeftButton and self.m_flag: self.move(QMouseEvent.globalPos() - self.m_Position) def mouseReleaseEvent(self, QMouseEvent): self.m_flag = False @staticmethod def show_image(img_src, label): try: ih, iw, _ = img_src.shape w = label.geometry().width() h = label.geometry().height() # keep original aspect ratio if iw/w > ih/h: scal = w / iw nw = w nh = int(scal * ih) img_src_ = cv2.resize(img_src, (nw, nh)) else: scal = h / ih nw = int(scal * iw) nh = h img_src_ = cv2.resize(img_src, (nw, nh)) frame = cv2.cvtColor(img_src_, cv2.COLOR_BGR2RGB) img = QImage(frame.data, frame.shape[1], frame.shape[0], frame.shape[2] * frame.shape[1], QImage.Format_RGB888) label.setPixmap(QPixmap.fromImage(img)) except Exception as e: print(repr(e)) def show_statistic(self, statistic_dic): try: self.resultWidget.clear() statistic_dic = sorted(statistic_dic.items(), key=lambda x: x[1], reverse=True) statistic_dic = [i for i in statistic_dic if i[1] > 0] results = [' '+str(i[0]) + ':' + str(i[1]) for i in statistic_dic] self.resultWidget.addItems(results) except Exception as e: print(repr(e)) def closeEvent(self, event): self.det_thread.jump_out = True config_file = 'config/setting.json' config = dict() config['iou'] = self.confSpinBox.value() config['conf'] = self.iouSpinBox.value() config['rate'] = self.rateSpinBox.value() config['check'] = self.checkBox.checkState() config['savecheck'] = self.saveCheckBox.checkState() config_json = json.dumps(config, ensure_ascii=False, indent=2) with open(config_file, 'w', encoding='utf-8') as f: f.write(config_json) MessageBox( self.closeButton, title='提示', text='正在关闭程序', time=2000, auto=True).exec_() sys.exit(0) if __name__ == "__main__": app = QApplication(sys.argv) myWin = MainWindow() myWin.show() # myWin.showMaximized() sys.exit(app.exec_())

import sys import json import os import warnings from datetime import datetime from PyQt5.QtCore import QUrl, Qt, QDateTime from PyQt5.QtWidgets import ( QApplication, QMainWindow, QLineEdit, QPushButton, QListWidget, QTabWidget, QVBoxLayout, QWidget, QHBoxLayout, QAction, QToolBar, QStatusBar, QProgressBar, QInputDialog, QFileDialog, QMessageBox ) from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage, QWebEngineSettings from PyQt5.QtNetwork import QNetworkProxy, QNetworkCookie # 修复代理设置导入问题 # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) class WebEnginePage(QWebEnginePage): def __init__(self, profile=None, parent=None): """修复参数传递问题:支持带profile和不带profile两种初始化方式""" if profile: super().__init__(profile, parent) # 使用profile初始化 else: super().__init__(parent) # 标准初始化 self.parent_window = parent.parent_window if parent and hasattr(parent, 'parent_window') else None def acceptNavigationRequest(self, url, _type, isMainFrame): # 检测视频流协议并调用播放器 if url.scheme() in ['rtmp', 'http-flv', 'ws-flv']: if self.parent_window: self.parent_window.play_with_jessibuca(url.toString()) return False return super().acceptNavigationRequest(url, _type, isMainFrame) def createWindow(self, type): # 在新标签页打开链接 if type == QWebEnginePage.WebBrowserTab: if self.parent_window: new_tab = self.parent_window.add_browser_tab("加载中...") return new_tab.page return super().createWindow(type) def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID): """修复:正确映射JavaScript控制台日志级别""" # 创建日志级别映射字典 level_map = { QWebEnginePage.InfoMessageLevel: "INFO", QWebEnginePage.WarningMessageLevel: "WARNING", QWebEnginePage.ErrorMessageLevel: "ERROR" } # 获取可读的日志级别名称 level_str = level_map.get(level, "UNKNOWN") log_msg = f"[JS {level_str}] {sourceID}:{lineNumber} - {message}" if self.parent_window: self.parent_window.status_bar.showMessage(log_msg, 5000) print(log_msg) # 同时输出到控制台 class WebEngineView(QWebEngineView): def __init__(self, parent=None): super().__init__(parent) self.parent_window = parent # 修复:正确传递profile参数 self.page = WebEnginePage(profile=self.parent_window.profile, parent=self) self.setPage(self.page) self.page.loadFinished.connect(self.inject_jessibuca) def inject_jessibuca(self): self.page.runJavaScript(""" if (typeof window.Jessibuca === 'undefined') { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/jessibuca@latest/dist/jessibuca.js'; script.onerror = () => console.error('Jessibuca加载失败'); document.head.appendChild(script); const container = document.createElement('div'); container.id = 'jessibuca-container'; container.style.position = 'fixed'; container.style.zIndex = '99999'; container.style.top = '0'; container.style.left = '0'; container.style.width = '100%'; container.style.height = '100%'; container.style.backgroundColor = 'black'; container.style.display = 'none'; document.body.appendChild(container); } """) def createWindow(self, type): # 创建新标签页 if type == QWebEnginePage.WebBrowserTab: new_tab = WebEngineView(self.parent_window) # 修复:直接返回page属性而非调用page() return new_tab.page return super().createWindow(type) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("B站多账号浏览器") self.setMinimumSize(1200, 800) # 初始化数据存储 self.account_db = {} self.cookies_db = [] # 创建持久化Cookie配置 self.cookie_storage_path = "cookies_storage" os.makedirs(self.cookie_storage_path, exist_ok=True) self.profile = QWebEngineProfile("BiliCookieProfile", self) self.profile.setPersistentCookiesPolicy(QWebEngineProfile.ForcePersistentCookies) self.profile.setPersistentStoragePath(self.cookie_storage_path) # 关键性能优化1: 全局禁用网络代理[8](@ref) QNetworkProxy.setApplicationProxy(QNetworkProxy(QNetworkProxy.NoProxy)) # 关键性能优化2: 启用HTTP内存缓存[9](@ref) self.profile.setHttpCacheType(QWebEngineProfile.MemoryHttpCache) # 启用HTML5支持的设置 profile_settings = self.profile.settings() profile_settings.setAttribute(QWebEngineSettings.PlaybackRequiresUserGesture, False) profile_settings.setAttribute(QWebEngineSettings.FullScreenSupportEnabled, True) profile_settings.setAttribute(QWebEngineSettings.WebGLEnabled, True) profile_settings.setAttribute(QWebEngineSettings.Accelerated2dCanvasEnabled, True) profile_settings.setAttribute(QWebEngineSettings.PluginsEnabled, True) profile_settings.setAttribute(QWebEngineSettings.AllowRunningInsecureContent, True) # 主界面布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 导航工具栏 navbar = QToolBar("导航栏") navbar.setMovable(False) # 禁止工具栏拖动提升性能 self.addToolBar(navbar) back_btn = QAction("←", self) back_btn.triggered.connect(self.navigate_back) navbar.addAction(back_btn) forward_btn = QAction("→", self) forward_btn.triggered.connect(self.navigate_forward) navbar.addAction(forward_btn) reload_btn = QAction("↻", self) reload_btn.triggered.connect(self.reload_page) navbar.addAction(reload_btn) home_btn = QAction("🏠", self) home_btn.triggered.connect(self.navigate_home) navbar.addAction(home_btn) navbar.addSeparator() self.url_bar = QLineEdit("https://www.bilibili.com") self.url_bar.setPlaceholderText("输入网址或搜索内容...") self.url_bar.returnPressed.connect(self.load_url) navbar.addWidget(self.url_bar) # 浏览器标签页 self.browser_tabs = QTabWidget() self.browser_tabs.setTabsClosable(True) self.browser_tabs.tabCloseRequested.connect(self.close_tab) self.browser_tabs.currentChanged.connect(self.update_url_bar) # 关键性能优化3: 启用标签页滚动 self.browser_tabs.setUsesScrollButtons(True) main_layout.addWidget(self.browser_tabs, 8) # 添加初始标签页 self.add_browser_tab("首页", "https://www.bilibili.com") # 管理面板 self.tabs = QTabWidget() self.cookie_list = QListWidget() self.tabs.addTab(self.cookie_list, "Cookie列表") self.account_list = QListWidget() self.tabs.addTab(self.account_list, "账号切换") self.account_list.itemClicked.connect(self.switch_account) main_layout.addWidget(self.tabs, 2) # 功能按钮 btn_layout = QHBoxLayout() self.import_btn = QPushButton("导入Cookie") self.import_btn.clicked.connect(self.import_cookies) self.save_btn = QPushButton("保存账号") self.save_btn.clicked.connect(self.save_account) self.export_btn = QPushButton("导出Cookie") self.export_btn.clicked.connect(self.export_cookies) self.new_tab_btn = QPushButton("新建标签页") self.new_tab_btn.clicked.connect(self.create_new_tab) for btn in [self.import_btn, self.save_btn, self.export_btn, self.new_tab_btn]: btn_layout.addWidget(btn) main_layout.addLayout(btn_layout) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.progress_bar = QProgressBar() self.progress_bar.setMaximum(100) self.progress_bar.setVisible(False) self.progress_bar.setFixedWidth(200) self.status_bar.addPermanentWidget(self.progress_bar) # 加载数据 self.load_accounts() def add_browser_tab(self, title, url=None): browser = WebEngineView(self) browser.titleChanged.connect(lambda title, b=browser: self.update_tab_title(b, title)) browser.loadProgress.connect(self.update_progress) browser.loadFinished.connect(self.on_load_finished) index = self.browser_tabs.addTab(browser, title) self.browser_tabs.setCurrentIndex(index) # 关键性能优化4: 延迟加载提升启动速度 if url: QApplication.processEvents() # 确保UI更新 browser.load(QUrl(url)) return browser def update_tab_title(self, browser, title): index = self.browser_tabs.indexOf(browser) if index != -1: self.browser_tabs.setTabText(index, title[:15] + "..." if len(title) > 15 else title) def update_progress(self, progress): self.progress_bar.setVisible(progress < 100) self.progress_bar.setValue(progress) def close_tab(self, index): """修复资源释放问题:移除多余的括号""" if self.browser_tabs.count() > 1: widget = self.browser_tabs.widget(index) # 关键修复:直接访问page属性而非调用page() page = widget.page if page: # 关键优化: 正确的资源释放顺序 profile = page.profile() profile.cookieStore().deleteAllCookies() # 先删除页面再删除视图 page.deleteLater() widget.deleteLater() self.browser_tabs.removeTab(index) else: self.create_new_tab() def create_new_tab(self): self.add_browser_tab("新标签页", "about:blank") def navigate_back(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.back() def navigate_forward(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.forward() def reload_page(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.reload() def navigate_home(self): self.load_url("https://www.bilibili.com") def on_load_finished(self, success): browser = self.sender() if browser and self.browser_tabs.currentWidget() == browser: current_url = browser.url().toString() self.url_bar.setText(current_url) self.status_bar.showMessage("页面加载完成" if success else "页面加载失败", 2000) # 关键优化: 登录页面自动处理 if "login" in current_url or "passport" in current_url: self.status_bar.showMessage("检测到登录页面,请完成登录", 5000) self.auto_handle_login_page(browser) def auto_handle_login_page(self, browser): """自动处理登录页面的JavaScript""" browser.page.runJavaScript(""" // 尝试自动填充已知的登录表单 const loginForm = document.querySelector('form[action*="login"]'); if (loginForm) { // 尝试填充测试账号 const usernameInput = loginForm.querySelector('input[name="username"], input[name="user"]'); const passwordInput = loginForm.querySelector('input[name="password"]'); if (usernameInput && passwordInput) { usernameInput.value = "test_account"; passwordInput.value = "test_password"; console.log("自动填充了登录表单"); } } """) def update_url_bar(self): current_browser = self.browser_tabs.currentWidget() if current_browser: current_url = current_browser.url().toString() self.url_bar.setText(current_url) # 登录页面特殊处理 if "login" in current_url or "passport" in current_url: self.url_bar.setStyleSheet("background-color: #FFF8E1;") else: self.url_bar.setStyleSheet("") def load_url(self, url_text=None): if url_text is None: url_text = self.url_bar.text().strip() if not url_text: return # 关键优化: 更智能的URL处理[10](@ref) if not url_text.startswith(("https://", "https://", "file://", "ftp://")): if "." in url_text: # 包含域名 url_text = "https://" + url_text else: # 可能是搜索内容 url_text = f"https://www.bilibili.com/search?keyword={url_text}" current_browser = self.browser_tabs.currentWidget() if current_browser: current_browser.load(QUrl(url_text)) self.status_bar.showMessage(f"正在加载: {url_text}", 3000) def play_with_jessibuca(self, stream_url): """使用Jessibuca播放视频流""" current_browser = self.browser_tabs.currentWidget() if not current_browser: return self.status_bar.showMessage("Jessibuca播放中...", 3000) current_browser.page.runJavaScript(f""" const container = document.getElementById('jessibuca-container'); if (container) {{ container.style.display = 'block'; if (!window.jessibucaPlayer) {{ window.jessibucaPlayer = new Jessibuca({{ container: container, videoBuffer: 0.2, isResize: true, text: '直播加载中...', decoder: 'ffmpeg.js', forceNoOffscreen: true }}); }} window.jessibucaPlayer.play('{stream_url}'); }} """) def import_cookies(self): """导入Cookie JSON文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择Cookie文件", "", "JSON文件 (*.json)" ) if file_path: try: with open(file_path, "r", encoding='utf-8') as f: cookies = json.load(f) if isinstance(cookies, list): self.cookies_db = cookies self.cookie_list.clear() self.cookie_list.addItems([f"{c['name']}: {c['value'][:10]}..." for c in cookies]) self.status_bar.showMessage(f"成功导入 {len(cookies)} 个Cookie", 3000) self.inject_cookies(cookies) else: QMessageBox.warning(self, "错误", "无效的Cookie格式") except Exception as e: QMessageBox.critical(self, "导入失败", f"错误: {str(e)}") def export_cookies(self): """导出Cookie到文件""" file_path, _ = QFileDialog.getSaveFileName( self, "保存Cookie文件", "", "JSON文件 (*.json)" ) if file_path: try: cookies = self.get_current_cookies() with open(file_path, "w", encoding='utf-8') as f: json.dump(cookies, f, indent=2, ensure_ascii=False) self.status_bar.showMessage("Cookie导出成功", 3000) except Exception as e: QMessageBox.critical(self, "导出失败", f"错误: {str(e)}") def get_current_cookies(self): """获取当前标签页的Cookie(模拟)""" # 实际实现需要异步获取,这里简化处理 return self.cookies_db if self.cookies_db else [] def inject_cookies(self, cookies): """将Cookie注入当前页面 - 修复登录问题[9](@ref)""" current_browser = self.browser_tabs.currentWidget() if current_browser: store = current_browser.page.profile().cookieStore() # 先清除现有Cookie store.deleteAllCookies() # 异步注入新的Cookie for cookie_data in cookies: # 创建Qt的Cookie对象 qt_cookie = QNetworkCookie( cookie_data['name'].encode('utf-8'), cookie_data['value'].encode('utf-8') ) # 设置Cookie属性 if 'domain' in cookie_data: qt_cookie.setDomain(cookie_data['domain']) if 'path' in cookie_data: qt_cookie.setPath(cookie_data['path']) if 'expiry' in cookie_data: # 转换为QDateTime expiry = QDateTime.fromSecsSinceEpoch(cookie_data['expiry']) qt_cookie.setExpirationDate(expiry) # 设置安全属性 qt_cookie.setSecure(cookie_data.get('secure', False)) qt_cookie.setHttpOnly(cookie_data.get('httpOnly', False)) # 注入Cookie store.setCookie(qt_cookie, QUrl(cookie_data.get('url', 'https://www.bilibili.com'))) self.status_bar.showMessage("Cookie注入成功,请刷新页面", 3000) def save_account(self): """保存当前账号配置""" account_name, ok = QInputDialog.getText( self, "保存账号", "输入账号名称:" ) if ok and account_name: cookies = self.get_current_cookies() if cookies: self.account_db[account_name] = { "cookies": cookies, "saved_date": datetime.now().isoformat() } self.account_list.addItem(account_name) # 持久化存储账号数据 try: with open("accounts.json", "w", encoding='utf-8') as f: json.dump(self.account_db, f, indent=2, ensure_ascii=False) QMessageBox.information(self, "成功", "账号保存成功") except Exception as e: QMessageBox.critical(self, "保存失败", f"账号保存失败: {str(e)}") else: QMessageBox.warning(self, "错误", "没有获取到有效Cookie") def switch_account(self, item): """切换账号""" account_name = item.text() if account_name in self.account_db: cookies = self.account_db[account_name].get("cookies", []) self.inject_cookies(cookies) self.status_bar.showMessage(f"已切换至账号: {account_name}", 3000) # 自动刷新当前页面 self.reload_page() else: QMessageBox.warning(self, "错误", "找不到该账号的Cookie信息") def load_accounts(self): """从文件加载保存的账号""" try: if os.path.exists("accounts.json"): with open("accounts.json", "r", encoding='utf-8') as f: self.account_db = json.load(f) self.account_list.addItems(self.account_db.keys()) except Exception as e: print(f"加载账号失败: {str(e)}") def closeEvent(self, event): """窗口关闭时释放全局资源[8](@ref)""" # 清理HTTP缓存和访问记录 self.profile.clearHttpCache() self.profile.clearAllVisitedLinks() # 保存账号数据 try: with open("accounts.json", "w", encoding='utf-8') as f: json.dump(self.account_db, f, indent=2, ensure_ascii=False) except Exception as e: print(f"保存账号失败: {str(e)}") super().closeEvent(event) if __name__ == "__main__": # 关键性能优化: 启用WebEngine调试日志[9](@ref) os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging" app = QApplication(sys.argv) # 关键性能优化: 启用Qt内置的OpenGL渲染[7](@ref) app.setAttribute(Qt.AA_UseOpenGLES) window = MainWindow() window.show() sys.exit(app.exec_())

import torch import torch.nn as nn import torch.optim as optim from torchtext.datasets import IMDB import os # 设置本地数据集路径 data_path = "C:\sdxx" # 加载数据 train_iter, test_iter = IMDB(root=data_path, split=('train', 'test')) from torchtext.data.utils import get_tokenizer from torchtext.vocab import build_vocab_from_iterator, GloVe import matplotlib.pyplot as plt from torch.utils.data.dataset import random_split import numpy as np import time from torch.utils.data import DataLoader import os from sklearn.metrics import confusion_matrix, classification_report # 禁用警告信息 import warnings warnings.filterwarnings('ignore') import torchtext torchtext.disable_torchtext_deprecation_warning() # 设置随机种子,保证结果可复现 torch.manual_seed(42) np.random.seed(42) # 检查GPU是否可用 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(f"使用设备: {device}") # 1. 数据预处理 tokenizer = get_tokenizer('basic_english') train_iter = IMDB(split='train') # 检查数据集格式 first_example = next(iter(train_iter)) print(f"数据集格式示例: {first_example}") # 构建词汇表 def yield_tokens(data_iter): for _, text in data_iter: yield tokenizer(text) vocab = build_vocab_from_iterator(yield_tokens(train_iter), min_freq=5, specials=["", "<unk>"]) vocab.set_default_index(vocab["<unk>"]) # 加载预训练词向量 (300d) print("加载预训练GloVe词向量...") vectors = GloVe(name='6B', dim=300) pretrained_embedding = vectors.get_vecs_by_tokens(vocab.get_itos()) print(f"预训练词向量形状: {pretrained_embedding.shape}") # 超参数 - 已优化配置 MAX_SEQ_LEN = 256 # 增加最大序列长度 EMBEDDING_DIM = 300 HIDDEN_DIM = 192 # 增加隐藏层维度 OUTPUT_DIM = 1 BATCH_SIZE = 32 # 减小批次大小增加随机性 EPOCHS = 15 # 增加最大训练轮次 LR = 5e-4 # 降低初始学习率 DROPOUT = 0.6 # 增加Dropout比率 accumulation_steps = 4 # 梯度累积步数 LSTM_LAYERS = 3 # 增加LSTM层数 WEIGHT_DECAY = 5e-4 # 增加权重衰减 # 数据批量处理函数 - 动态长度+标签处理 def collate_batch(batch): # 标签处理:将标签1映射为0(负例),标签2映射为1(正例) labels = [1 if example[0] == 2 else 0 for example in batch] labels = torch.tensor(labels).to(device) # 处理文本 - 动态长度 texts = [tokenizer(example[1]) for example in batch] # 计算当前批次平均长度,动态调整截断值 avg_len = sum(len(t) for t in texts) // len(texts) dynamic_len = min(avg_len + 25, MAX_SEQ_LEN) # 增加动态长度上限 texts = [vocab(text) for text in texts] texts = [torch.tensor([i for i in text[:dynamic_len]]) for text in texts] texts = torch.nn.utils.rnn.pad_sequence(texts, batch_first=True, padding_value=vocab[""]).to(device) return texts, labels # 重新加载训练集和测试集 train_iter = IMDB(split='train') test_iter = IMDB(split='test') # 使用原始数据(若需数据增强,可取消注释以下部分) train_dataset = list(train_iter) test_dataset = list(test_iter) # 划分训练集和验证集 train_size = int(0.8 * len(train_dataset)) val_size = len(train_dataset) - train_size train_dataset, val_dataset = random_split(train_dataset, [train_size, val_size]) # 创建数据加载器 train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_batch) val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, collate_fn=collate_batch) test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, collate_fn=collate_batch) # 检查数据分布 def check_data_distribution(): train_labels = [label for (label, _) in train_dataset] val_labels = [label for (label, _) in val_dataset] test_labels = [label for (label, _) in test_dataset] # 计算正例比例 train_pos_ratio = sum([1 for lbl in train_labels if lbl == 2]) / len(train_labels) val_pos_ratio = sum([1 for lbl in val_labels if lbl == 2]) / len(val_labels) test_pos_ratio = sum([1 for lbl in test_labels if lbl == 2]) / len(test_labels) print(f"训练集大小: {len(train_dataset)}, 正例比例: {train_pos_ratio:.4f}") print(f"验证集大小: {len(val_dataset)}, 正例比例: {val_pos_ratio:.4f}") print(f"测试集大小: {len(test_dataset)}, 正例比例: {test_pos_ratio:.4f}") # 检查批次数据 batch = next(iter(train_loader)) texts, labels = batch print(f"批次示例 - 文本形状: {texts.shape}, 标签形状: {labels.shape}") print(f"标签示例: {labels[:10].cpu().numpy()}") check_data_distribution() # 2. 改进模型结构 - 双向LSTM+注意力机制 class AttentionLSTM(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim, dropout=0.5, pretrained=None, num_layers=2): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim) # 使用预训练词向量 if pretrained is not None: self.embedding.weight.data.copy_(pretrained) self.embedding.weight.requires_grad = True # 微调预训练向量 self.lstm = nn.LSTM( embedding_dim, hidden_dim, batch_first=True, bidirectional=True, # 双向LSTM num_layers=num_layers, # 增加LSTM层数 dropout=dropout # 层间dropout ) # 注意力机制 self.attention = nn.Sequential( nn.Linear(hidden_dim * 2, hidden_dim), nn.Tanh(), nn.Linear(hidden_dim, 1), nn.Softmax(dim=1) ) self.dropout = nn.Dropout(dropout) # 添加额外的全连接层 self.fc1 = nn.Linear(hidden_dim * 2, hidden_dim) self.fc2 = nn.Linear(hidden_dim, output_dim) def forward(self, text): # text: [batch_size, seq_len] embedded = self.embedding(text) # [batch_size, seq_len, embedding_dim] # LSTM输出 lstm_out, _ = self.lstm(embedded) # [batch_size, seq_len, hidden_dim*2] # 注意力权重计算 attn_weights = self.attention(lstm_out) # [batch_size, seq_len, 1] # 加权求和得到上下文向量 context = torch.bmm(attn_weights.transpose(1, 2), lstm_out).squeeze(1) # [batch_size, hidden_dim*2] # 多层全连接 output = self.dropout(self.fc1(context)) output = self.fc2(output) # [batch_size, output_dim] return output # 3. 训练配置 - 优化器与调度器 def train_model(model_id=0): # 初始化模型 model = AttentionLSTM( vocab_size, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM, DROPOUT, pretrained_embedding, LSTM_LAYERS ).to(device) # 优化器 - 使用调整后的权重衰减 optimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY) # 学习率调度器 - 改进版 total_steps = len(train_loader) * EPOCHS warmup_steps = int(total_steps * 0.15) # 增加warmup比例 def warmup_cosine(current_step): if current_step < warmup_steps: return float(current_step) / float(max(1, warmup_steps)) progress = float(current_step - warmup_steps) / float(max(1, total_steps - warmup_steps)) return max(0.0, 0.5 * (1.0 + np.cos(np.pi * progress))) scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=warmup_cosine) # 早停机制 - 更严格的配置 early_stopping = EarlyStopping(patience=7, delta=0.005, verbose=True, path=f'best_model_{model_id}.pt') # 训练循环 train_losses = [] val_losses = [] val_accs = [] print(f"\n开始训练模型 {model_id + 1}...") start_time = time.time() for epoch in range(EPOCHS): # 训练阶段 model.train() train_loss = 0.0 train_correct = 0 train_total = 0 for i, (texts, labels) in enumerate(train_loader): optimizer.zero_grad() outputs = model(texts).squeeze(1) loss = criterion(outputs, labels.float()) loss = loss / accumulation_steps # 梯度累积 loss.backward() # 梯度裁剪(防止梯度爆炸) nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 累积一定步数后更新参数 if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad() scheduler.step() # 更新学习率 train_loss += loss.item() * accumulation_steps preds = torch.round(torch.sigmoid(outputs)) train_total += labels.size(0) train_correct += (preds == labels).sum().item() # 计算训练准确率 train_acc = train_correct / train_total # 验证阶段 model.eval() val_loss = 0.0 val_correct = 0 val_total = 0 with torch.no_grad(): for texts, labels in val_loader: outputs = model(texts).squeeze(1) loss = criterion(outputs, labels.float()) val_loss += loss.item() preds = torch.round(torch.sigmoid(outputs)) val_total += labels.size(0) val_correct += (preds == labels).sum().item() # 计算平均损失和准确率 avg_train_loss = train_loss / len(train_loader) avg_val_loss = val_loss / len(val_loader) val_acc = val_correct / val_total # 记录结果 train_losses.append(avg_train_loss) val_losses.append(avg_val_loss) val_accs.append(val_acc) # 打印当前学习率 current_lr = optimizer.param_groups[0]['lr'] print(f"Epoch: {epoch + 1}/{EPOCHS}, LR: {current_lr:.7f}, " f"Train Loss: {avg_train_loss:.4f}, Train Acc: {train_acc:.4f}, " f"Val Loss: {avg_val_loss:.4f}, Val Acc: {val_acc:.4f}") # 早停判断 early_stopping(avg_val_loss, model) if early_stopping.early_stop: print("Early stopping") break total_time = time.time() - start_time print(f"模型 {model_id + 1} 训练完成,总耗时: {total_time:.2f}秒") return { 'model': model, 'train_losses': train_losses, 'val_losses': val_losses, 'val_accs': val_accs, 'best_val_loss': early_stopping.val_loss_min, 'best_val_acc': max(val_accs) } # 早停机制 class EarlyStopping: def __init__(self, patience=5, verbose=False, delta=0.0, path='best_model.pt'): self.patience = patience self.verbose = verbose self.counter = 0 self.best_score = None self.early_stop = False self.val_loss_min = np.Inf self.delta = delta self.path = path def __call__(self, val_loss, model): score = -val_loss if self.best_score is None: self.best_score = score self.save_checkpoint(val_loss, model) elif score < self.best_score + self.delta: self.counter += 1 print(f'EarlyStopping counter: {self.counter} out of {self.patience}') if self.counter >= self.patience: self.early_stop = True else: self.best_score = score self.save_checkpoint(val_loss, model) self.counter = 0 def save_checkpoint(self, val_loss, model): if self.verbose: print(f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}). Saving model ...') torch.save(model.state_dict(), self.path) self.val_loss_min = val_loss # 集成模型预测 def ensemble_predict(models, texts): probs = [] for model in models: model.eval() with torch.no_grad(): output = model(texts).squeeze(1) probs.append(torch.sigmoid(output)) # 平均概率 avg_prob = torch.mean(torch.stack(probs), dim=0) return torch.round(avg_prob) # 训练多个模型进行集成 criterion = nn.BCEWithLogitsLoss() num_models = 3 # 集成3个模型 models_info = [] for i in range(num_models): model_info = train_model(i) models_info.append(model_info) # 加载最佳模型并在测试集上评估 ensemble_models = [] for i in range(num_models): model = AttentionLSTM( vocab_size, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM, DROPOUT, pretrained_embedding, LSTM_LAYERS ).to(device) model.load_state_dict(torch.load(f'best_model_{i}.pt')) ensemble_models.append(model) # 测试集评估 test_correct = 0 test_total = 0 all_preds = [] all_labels = [] with torch.no_grad(): for texts, labels in test_loader: preds = ensemble_predict(ensemble_models, texts) test_total += labels.size(0) test_correct += (preds == labels).sum().item() all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) test_accuracy = test_correct / test_total print(f"\n集成模型最终测试准确率: {test_accuracy:.4f}") # 5. 可视化 plt.figure(figsize=(18, 5)) # 损失曲线 plt.subplot(1, 3, 1) for i, info in enumerate(models_info): plt.plot(range(1, len(info['train_losses']) + 1), info['train_losses'], f'{i + 1}-', label=f'Train Loss {i + 1}') plt.plot(range(1, len(info['val_losses']) + 1), info['val_losses'], f'{i + 1}--', label=f'Val Loss {i + 1}') plt.title('Training and Validation Loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.grid(True) # 准确率曲线 plt.subplot(1, 3, 2) for i, info in enumerate(models_info): plt.plot(range(1, len(info['val_accs']) + 1), info['val_accs'], f'{i + 1}-', label=f'Val Acc {i + 1}') plt.axhline(y=test_accuracy, color='purple', linestyle='--', label=f'Test Accuracy: {test_accuracy:.4f}') plt.title('Validation and Test Accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.grid(True) # 混淆矩阵 plt.subplot(1, 3, 3) cm = confusion_matrix(all_labels, all_preds) plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues) plt.title('Confusion matrix') plt.colorbar() classes = ['Negative', 'Positive'] tick_marks = np.arange(len(classes)) plt.xticks(tick_marks, classes, rotation=45) plt.yticks(tick_marks, classes) # 在混淆矩阵上标注数字 thresh = cm.max() / 2. for i in range(cm.shape[0]): for j in range(cm.shape[1]): plt.text(j, i, format(cm[i, j], 'd'), horizontalalignment="center", color="white" if cm[i, j] > thresh else "black") plt.ylabel('True label') plt.xlabel('Predicted label') plt.tight_layout() plt.savefig('training_metrics.png') # 保存图表 plt.show() # 打印分类报告 report = classification_report(all_labels, all_preds, target_names=['Negative', 'Positive']) print("\n分类报告:") print(report) # 单个样本测试 def predict_sentiment(models, vocab, tokenizer, sentence, max_len=MAX_SEQ_LEN): tokens = tokenizer(sentence) indexed = [vocab[t] for t in tokens] tensor = torch.tensor(indexed).unsqueeze(0).to(device) # [1, seq_len] # 动态长度处理 dynamic_len = min(len(indexed) + 25, max_len) tensor = torch.nn.utils.rnn.pad_sequence([tensor.squeeze(0)], batch_first=True, padding_value=vocab[""]).to(device) # 集成预测 probs = [] for model in models: model.eval() with torch.no_grad(): output = model(tensor).squeeze(1) probs.append(torch.sigmoid(output).item()) avg_prob = sum(probs) / len(probs) return "Positive" if avg_prob > 0.5 else "Negative", avg_prob # 测试示例 test_sentences = [ "This movie is amazing! I love it.", "Terrible film, waste of money and time.", "The plot was confusing but the visuals were stunning.", "I've seen better, but it wasn't terrible.", "Absolutely fantastic - one of the best I've seen!", "The acting was good, but the story had no depth." ] print("\n样本测试结果:") for sentence in test_sentences: sentiment, prob = predict_sentiment(ensemble_models, vocab, tokenizer, sentence) print(f"影评: {sentence}") print(f"情感: {sentiment}, 置信度: {prob:.4f}") print("-" * 50) # 保存模型和词汇表 for i, model in enumerate(ensemble_models): torch.save(model.state_dict(), f'ensemble_model_{i}.pt') torch.save(vocab, 'vocab.pth') print("\n模型和词汇表已保存!") # 预测自定义影评函数 def predict_custom_review(models): print("\n输入影评文本 (输入'q'退出):") while True: review = input("> ") if review.lower() == 'q': break sentiment, prob = predict_sentiment(models, vocab, tokenizer, review) print(f"情感: {sentiment}, 置信度: {prob:.4f}") print("-" * 50) # 启动交互式预测 predict_custom_review(ensemble_models) 这段代码报错:C:\anaconda\envs\py39\python.exe C:\Users\32398\PyCharmMiscProject\深度学习\期末.py C:\anaconda\envs\py39\lib\site-packages\torchtext\datasets\__init__.py:4: UserWarning: /!\ IMPORTANT WARNING ABOUT TORCHTEXT STATUS /!\ Torchtext is deprecated and the last released version will be 0.18 (this one). You can silence this warning by calling the following at the beginnign of your scripts: import torchtext; torchtext.disable_torchtext_deprecation_warning() warnings.warn(torchtext._TORCHTEXT_DEPRECATION_MSG) C:\anaconda\envs\py39\lib\site-packages\torchtext\data\__init__.py:4: UserWarning: /!\ IMPORTANT WARNING ABOUT TORCHTEXT STATUS /!\ Torchtext is deprecated and the last released version will be 0.18 (this one). You can silence this warning by calling the following at the beginnign of your scripts: import torchtext; torchtext.disable_torchtext_deprecation_warning() warnings.warn(torchtext._TORCHTEXT_DEPRECATION_MSG) C:\anaconda\envs\py39\lib\site-packages\torchdata\datapipes\__init__.py:18: UserWarning: ################################################################################ WARNING! The 'datapipes', 'dataloader2' modules are deprecated and will be removed in a future torchdata release! Please see https://github.com/pytorch/data/issues/1196 to learn more and leave feedback. ################################################################################ deprecation_warning() C:\anaconda\envs\py39\lib\site-packages\torchtext\vocab\__init__.py:4: UserWarning: /!\ IMPORTANT WARNING ABOUT TORCHTEXT STATUS /!\ Torchtext is deprecated and the last released version will be 0.18 (this one). You can silence this warning by calling the following at the beginnign of your scripts: import torchtext; torchtext.disable_torchtext_deprecation_warning() warnings.warn(torchtext._TORCHTEXT_DEPRECATION_MSG) C:\anaconda\envs\py39\lib\site-packages\torchtext\utils.py:4: UserWarning: /!\ IMPORTANT WARNING ABOUT TORCHTEXT STATUS /!\ Torchtext is deprecated and the last released version will be 0.18 (this one). You can silence this warning by calling the following at the beginnign of your scripts: import torchtext; torchtext.disable_torchtext_deprecation_warning() warnings.warn(torchtext._TORCHTEXT_DEPRECATION_MSG) ImportError: DLL load failed while importing _multiarray_umath: 找不到指定的模块。 Traceback (most recent call last): File "C:\Users\32398\PyCharmMiscProject\深度学习\期末.py", line 13, in <module> import matplotlib.pyplot as plt File "C:\anaconda\envs\py39\lib\site-packages\matplotlib\__init__.py", line 161, in <module> from . import _api, _version, cbook, _docstring, rcsetup File "C:\anaconda\envs\py39\lib\site-packages\matplotlib\rcsetup.py", line 27, in <module> from matplotlib.colors import Colormap, is_color_like File "C:\anaconda\envs\py39\lib\site-packages\matplotlib\colors.py", line 56, in <module> from matplotlib import _api, _cm, cbook, scale File "C:\anaconda\envs\py39\lib\site-packages\matplotlib\scale.py", line 22, in <module> from matplotlib.ticker import ( File "C:\anaconda\envs\py39\lib\site-packages\matplotlib\ticker.py", line 138, in <module> from matplotlib import transforms as mtransforms File "C:\anaconda\envs\py39\lib\site-packages\matplotlib\transforms.py", line 49, in <module> from matplotlib._path import ( ImportError: numpy.core.multiarray failed to import 进程已结束,退出代码为 1

import os import sys import shutil import tempfile import logging from fastapi import FastAPI, File, UploadFile, HTTPException from fastapi.responses import JSONResponse, FileResponse import time import json from pathlib import Path import uvicorn import warnings import torch from inference import CombinedPredictor, process_single_image, process_video # 直接导入检测类和函数 # 配置日志 logging.basicConfig( filename='api_errors.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s' ) # 忽略弃用警告 warnings.filterwarnings("ignore", category=DeprecationWarning) app = FastAPI(title="目标检测服务", description="通过FastAPI调用inference.py进行目标检测") # 配置参数 OUTPUT_BASE_DIR = "api_results" # 输出结果的基础目录 def get_resource_path(relative_path): """获取资源文件的路径,兼容打包后的环境""" if getattr(sys, 'frozen', False): # 如果是打包后的环境 base_path = sys._MEIPASS else: # 如果是开发环境 base_path = os.path.dirname(os.path.abspath(__file__)) return os.path.join(base_path, relative_path) # 全局预测器 predictor = None @app.on_event("startup") def startup_event(): """应用启动时初始化预测器""" global predictor os.makedirs(OUTPUT_BASE_DIR, exist_ok=True) try: # 初始化预测器 device = "cuda" if torch.cuda.is_available() else "cpu" predictor = CombinedPredictor( yolo_model_path=get_resource_path("ultralytics/best.pt"), wsdan_model_path=get_resource_path("models/model.ckpt"), num_classes=60, class_names_path=get_resource_path("models/classes.txt"), device=device, conf_threshold=0.2, nms_threshold=0.45 ) print("预测器初始化成功") except Exception as e: print(f"预测器初始化失败: {str(e)}") raise HTTPException(status_code=500, detail=f"预测器初始化失败: {str(e)}") @app.post("/detect/image/") async def detect_image(file: UploadFile = File(...)): """处理单张图像的检测请求""" return await process_file(file, is_video=False) @app.post("/detect/video/") async def detect_video(file: UploadFile = File(...)): """处理视频的检测请求""" return await process_file(file, is_video=True) async def process_file(file: UploadFile, is_video: bool): """处理上传的文件并调用检测函数""" global predictor try: # 创建临时目录 with tempfile.TemporaryDirectory() as temp_dir: temp_file_path = os.path.join(temp_dir, file.filename) # 保存上传的文件到临时目录 with open(temp_file_path, "wb") as buffer: shutil.copyfileobj(file.file, buffer) # 创建唯一的输出目录 timestamp = int(time.time()) output_dir = os.path.join(OUTPUT_BASE_DIR, f"result_{timestamp}") os.makedirs(output_dir, exist_ok=True) # 打印调试信息 print(f"临时文件路径: {temp_file_path}") print(f"输出目录: {output_dir}") # 检查预测器是否初始化 if predictor is None: raise HTTPException(status_code=500, detail="预测器未初始化") # 设置参数 box_color = (0, 255, 0) # BGR格式 box_thickness = 2 text_color = (0, 255, 0) text_size = 0.5 text_font = "FONT_HERSHEY_SIMPLEX" # 处理文件 if is_video: output_path = process_video( predictor=predictor, video_path=temp_file_path, output_dir=output_dir, box_color=box_color, box_thickness=box_thickness, text_color=text_color, text_size=text_size, text_font=text_font ) else: output_path = process_single_image( predictor=predictor, image_path=temp_file_path, output_dir=output_dir, box_color=box_color, box_thickness=box_thickness, text_color=text_color, text_size=text_size, text_font=text_font ) # 查找结果文件 result_files = find_result_files(output_dir, file.filename) if not result_files: raise HTTPException(status_code=500, detail="未找到检测结果文件") # 返回结果文件信息 return { "status": "success", "input_file": file.filename, "output_dir": output_dir, "result_files": result_files } except Exception as e: error_msg = f"处理文件时出错: {str(e)}" logging.error(error_msg) # 记录异常到日志 print(error_msg) raise HTTPException(status_code=500, detail=error_msg) def find_result_files(output_dir, original_filename): """查找检测结果文件""" base_name = os.path.splitext(original_filename)[0] result_files = [] # 查找图像结果 img_result = os.path.join(output_dir, f"{base_name}_result.jpg") if os.path.exists(img_result): result_files.append({ "type": "image_result", "path": img_result, "url": f"/results/{os.path.basename(img_result)}" }) # 查找视频结果 video_result = os.path.join(output_dir, f"{base_name}_result.mp4") if os.path.exists(video_result): result_files.append({ "type": "video_result", "path": video_result, "url": f"/results/{os.path.basename(video_result)}" }) # 查找JSON结果 json_result = os.path.join(output_dir, f"{base_name}_first_detection.json") if os.path.exists(json_result): result_files.append({ "type": "json_result", "path": json_result, "url": f"/results/{os.path.basename(json_result)}" }) return result_files @app.get("/results/{file_name}") async def get_result_file(file_name: str): """获取结果文件""" file_path = os.path.join(OUTPUT_BASE_DIR, "*", file_name) matching_files = list(Path(OUTPUT_BASE_DIR).glob(f"**/{file_name}")) if not matching_files: raise HTTPException(status_code=404, detail="结果文件不存在") # 返回第一个匹配的文件 return FileResponse(matching_files[0]) @app.get("/") async def root(): """服务根端点,返回API信息""" return { "message": "目标检测API服务已启动", "available_endpoints": [ "/detect/image/", "/detect/video/", "/results/{file_name}" ] } # 添加命令行入口点 if __name__ == "__main__": uvicorn.run( "api:app", host="0.0.0.0", port=8000, log_level="info" )如何将该api.py代码打包为exe文件

from paddleocr import PaddleOCR import cv2 # 初始化OCR引擎(使用新版本参数) ocr = PaddleOCR( det_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_det", rec_model_dir=r"C:\Users\tao.yu02\.paddlex\official_models\PP-OCRv5_server_rec", use_angle_cls=False, # 关闭方向分类器 device='cpu' # 使用CPU运行(替代废弃的use_gpu) ) def recognize_text(image_path): # 读取图片 img = cv2.imread(image_path) if img is None: print(f"错误:无法读取图片 {image_path}") return # 执行OCR识别 result = ocr.ocr(img, cls=False) # 提取所有识别文本 texts = [] for line in result: if line: # 确保行非空 for word_info in line: text = word_info[1][0] # 提取文本内容 texts.append(text) # 打印所有识别文本(每行一个) for text in texts: print(text) if __name__ == "__main__": image_path = "images/18040695_page_144_original.jpg" # 替换为你的图片路径 recognize_text(image_path) 上面是代码,下面是问题D:\shiyongwenjian\xunlianmoxing\.venv\Scripts\python.exe D:\shiyongwenjian\xunlianmoxing\实验8.py D:\shiyongwenjian\xunlianmoxing\实验8.py:5: DeprecationWarning: The parameter det_model_dir has been deprecated and will be removed in the future. Please use text_detection_model_dir instead. ocr = PaddleOCR( D:\shiyongwenjian\xunlianmoxing\实验8.py:5: DeprecationWarning: The parameter rec_model_dir has been deprecated and will be removed in the future. Please use text_recognition_model_dir instead. ocr = PaddleOCR( D:\shiyongwenjian\xunlianmoxing\实验8.py:5: DeprecationWarning: The parameter use_angle_cls has been deprecated and will be removed in the future. Please use use_textline_orientation instead. ocr = PaddleOCR( ��Ϣ: ���ṩ��ģʽ�޷��ҵ��ļ��� D:\shiyongwenjian\xunlianmoxing\.venv\Lib\site-packages\paddle\utils\cpp_extension\extension_utils.py:715: UserWarning: No ccache found. Please be aware that recompiling all source files may be required. You can download and install ccache from: https://github.com/ccache/ccache/blob/master/doc/INSTALL.md warnings.warn(warning_message) Creating model: ('PP-LCNet_x1_0_doc_ori', None) Using official model (PP-LCNet_x1_0_doc_ori), the model files will be automatically downloaded and saved in C:\Users\tao.yu02\.paddlex\official_models. Creating model: ('UVDoc', None) The model(UVDoc) is not supported to run in MKLDNN mode! Using paddle instead! Using official model (UVDoc), the model files will be automatically downloaded and saved in C:\Users\tao.yu02\.paddlex\official_models. Creating model: ('PP-OCRv5_server_det', 'C:\\Users\\tao.yu02\\.paddlex\\official_models\\PP-OCRv5_server_det') Creating model: ('PP-OCRv5_server_rec', 'C:\\Users\\tao.yu02\\.paddlex\\official_models\\PP-OCRv5_server_rec') D:\shiyongwenjian\xunlianmoxing\实验8.py:21: DeprecationWarning: Please use predict instead. result = ocr.ocr(img, cls=False) Traceback (most recent call last): File "D:\shiyongwenjian\xunlianmoxing\实验8.py", line 38, in <module> recognize_text(image_path) File "D:\shiyongwenjian\xunlianmoxing\实验8.py", line 21, in recognize_text result = ocr.ocr(img, cls=False) ^^^^^^^^^^^^^^^^^^^^^^^ File "D:\shiyongwenjian\xunlianmoxing\.venv\Lib\site-packages\typing_extensions.py", line 2956, in wrapper return arg(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^ File "D:\shiyongwenjian\xunlianmoxing\.venv\Lib\site-packages\paddleocr\_pipelines\ocr.py", line 225, in ocr return self.predict(img, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TypeError: PaddleOCR.predict() got an unexpected keyword argument 'cls' 进程已结束,退出代码为 1

--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[5], line 5 3 import numpy as np 4 import matplotlib.pyplot as plt ----> 5 from scipy.spatial.distance import cdist 6 print(f"numpy: {np.__version__}, matplotlib: {plt.matplotlib.__version__}") 7 except ImportError as e: File D:\miniconda\lib\site-packages\scipy\spatial\__init__.py:110 1 """ 2 ============================================================= 3 Spatial algorithms and data structures (:mod:scipy.spatial) (...) 107 QhullError 108 """ # noqa: E501 --> 110 from ._kdtree import * 111 from ._ckdtree import * # type: ignore[import-not-found] 112 from ._qhull import * File D:\miniconda\lib\site-packages\scipy\spatial\_kdtree.py:4 1 # Copyright Anne M. Archibald 2008 2 # Released under the scipy license 3 import numpy as np ----> 4 from ._ckdtree import cKDTree, cKDTreeNode # type: ignore[import-not-found] 6 __all__ = ['minkowski_distance_p', 'minkowski_distance', 7 'distance_matrix', 8 'Rectangle', 'KDTree'] 11 def minkowski_distance_p(x, y, p=2): File _ckdtree.pyx:11, in init scipy.spatial._ckdtree() File D:\miniconda\lib\site-packages\scipy\sparse\__init__.py:300 294 # Original code by Travis Oliphant. 295 # Modified and extended by Ed Schofield, Robert Cimrman, 296 # Nathan Bell, and Jake Vanderplas. 298 import warnings as _warnings --> 300 from ._base import * 301 from ._csr import * 302 from ._csc import * File D:\miniconda\lib\site-packages\scipy\sparse\_base.py:5 1 """Base class for sparse matrices""" 3 import numpy as np ----> 5 from ._sputils import (asmatrix, check_reshape_kwargs, check_shape, 6 get_sum_dtype, isdense, isscalarlike, 7 matrix, validateaxis, getdtype) 9 from ._matrix import spmatrix 11 __all__ = ['isspmatrix', 'issparse', 'sparray', 12 'SparseWarning', 'SparseEfficiencyWarning'] File D:\miniconda\lib\site-packages\scipy\sparse\_sputils.py:10 8 from math import prod 9 import scipy.sparse as sp ---> 10 from scipy._lib._util import np_long, np_ulong 13 __all__ = ['upcast', 'getdtype', 'getdata', 'isscalarlike', 'isintlike', 14 'isshape', 'issequence', 'isdense', 'ismatrix', 'get_sum_dtype', 15 'broadcast_shapes'] 17 supported_dtypes = [np.bool_, np.byte, np.ubyte, np.short, np.ushort, np.intc, 18 np.uintc, np_long, np_ulong, np.longlong, np.ulonglong, 19 np.float32, np.float64, np.longdouble, 20 np.complex64, np.complex128, np.clongdouble] File D:\miniconda\lib\site-packages\scipy\_lib\_util.py:13 10 from typing import TypeAlias, TypeVar 12 import numpy as np ---> 13 from scipy._lib._array_api import array_namespace, is_numpy, xp_size 14 from scipy._lib._docscrape import FunctionDoc, Parameter 17 AxisError: type[Exception] File D:\miniconda\lib\site-packages\scipy\_lib\_array_api.py:18 15 import numpy.typing as npt 17 from scipy._lib import array_api_compat ---> 18 from scipy._lib.array_api_compat import ( 19 is_array_api_obj, 20 size as xp_size, 21 numpy as np_compat, 22 device as xp_device, 23 is_numpy_namespace as is_numpy, 24 is_cupy_namespace as is_cupy, 25 is_torch_namespace as is_torch, 26 is_jax_namespace as is_jax, 27 is_array_api_strict_namespace as is_array_api_strict 28 ) 30 __all__ = [ 31 '_asarray', 'array_namespace', 'assert_almost_equal', 'assert_array_almost_equal', 32 'get_xp_devices', (...) 38 'xp_take_along_axis', 'xp_unsupported_param_msg', 'xp_vector_norm', 39 ] 42 # To enable array API and strict array-like input validation File D:\miniconda\lib\site-packages\scipy\_lib\array_api_compat\numpy\__init__.py:1 ----> 1 from numpy import * # noqa: F403 3 # from numpy import * doesn't overwrite these builtin names 4 from numpy import abs, max, min, round # noqa: F401 File D:\miniconda\lib\site-packages\numpy\testing\__init__.py:11 8 from unittest import TestCase 10 from . import _private ---> 11 from ._private.utils import * 12 from ._private.utils import (_assert_valid_refcount, _gen_alignment_data) 13 from ._private import extbuild File D:\miniconda\lib\site-packages\numpy\testing\_private\utils.py:469 465 pprint.pprint(desired, msg) 466 raise AssertionError(msg.getvalue()) --> 469 @np._no_nep50_warning() 470 def assert_almost_equal(actual, desired, decimal=7, err_msg='', verbose=True): 471 """ 472 Raises an AssertionError if two items are not equal up to desired 473 precision. (...) 537 538 """ 539 __tracebackhide__ = True # Hide traceback for py.test File D:\miniconda\lib\site-packages\numpy\__init__.py:414, in __getattr__(attr) 410 raise AttributeError(__former_attrs__[attr], name=None) 412 if attr in __expired_attributes__: 413 raise AttributeError( --> 414 f"np.{attr} was removed in the NumPy 2.0 release. " 415 f"{__expired_attributes__[attr]}", 416 name=None 417 ) 419 if attr == "chararray": 420 warnings.warn( 421 "np.chararray is deprecated and will be removed from " 422 "the main namespace in the future. Use an array with a string " 423 "or bytes dtype instead.", DeprecationWarning, stacklevel=2) AttributeError: module 'numpy' has no attribute '_no_nep50_warning'

(TPV) leo@linux:~/TPVFormer-main$ python visualization/vis_scene.py --py-config config/tpv04_occupancy.py --work-dir out/tpv_occupancy --ckpt-path out/tpv_occupancy/latest.pth --save-path out/tpv_occupancy/videos --scene-name scene-0916 scene-0015 Set mlab.options.offscreen=True Namespace(ckpt_path='out/tpv_occupancy/latest.pth', py_config='config/tpv04_occupancy.py', save_path='out/tpv_occupancy/videos', scene_idx=0, scene_name=['scene-0916', 'scene-0015'], vis_train=False, work_dir='out/tpv_occupancy') /home/leo/TPVFormer-main/tpvformer04/modules/tpvformer_layer.py:69: UserWarning: The arguments feedforward_channels in BaseTransformerLayer has been deprecated, now you should set feedforward_channels and other FFN related arguments to a dict named ffn_cfgs. warnings.warn( /home/leo/TPVFormer-main/tpvformer04/modules/tpvformer_layer.py:69: UserWarning: The arguments ffn_dropout in BaseTransformerLayer has been deprecated, now you should set ffn_drop and other FFN related arguments to a dict named ffn_cfgs. warnings.warn( /home/leo/anaconda3/envs/TPV/lib/python3.8/site-packages/torch/functional.py:445: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at /opt/conda/conda-bld/pytorch_1634272068694/work/aten/src/ATen/native/TensorShape.cpp:2157.) return _VF.meshgrid(tensors, **kwargs) # type: ignore[attr-defined] <All keys matched successfully> ====== Loading NuScenes tables for version v1.0-trainval... Loading nuScenes-lidarseg... 32 category, 8 attribute, 4 visibility, 64386 instance, 12 sensor, 10200 calibrated_sensor, 2631083 ego_pose, 68 log, 850 scene, 34149 sample, 2631083 sample_data, 1166187 sample_annotation, 4 map, 34149 lidarseg, Done loading in 15.020 seconds. ====== Reverse indexing ... Done reverse indexing in 2.9 seconds. ====== visualizing scene-0916 236 processing frame 0 of scene 0 /home/leo/anaconda3/envs/TPV/lib/python3.8/site-packages/torch/utils/chec

最新推荐

recommend-type

APA多步垂直泊车与全局路径规划MPC控制算法联合仿真,开源版持续迭代更新

APA多步垂直泊车系统的仿真研究,重点探讨了Carsim与Matlab在自动泊车仿真中的联合应用。文章首先介绍了Carsim场景及车辆配置文件,展示了如何模拟车辆在不同道路条件下的行驶轨迹和碰撞风险。接着讨论了Simulink文件中的纵向逻辑控制,包括动力分配和刹车控制等。随后阐述了MPC横向控制算法文件的作用,即通过预测未来的系统状态来优化车辆的横向移动和控制。最后,文章讲解了路径规划算法及其全局规划方法,强调了基于规则和启发式的路径规划策略。文中提到的所有模型均开源,便于研究人员参考和学习。 适合人群:从事自动驾驶技术研发的研究人员和技术爱好者。 使用场景及目标:适用于希望深入了解自动泊车仿真技术的研究人员,特别是那些关注路径规划和MPC控制算法的人群。目标是帮助他们掌握Carsim与Matlab联合仿真的具体实现方法,从而应用于实际项目中。 其他说明:本文不仅提供了详细的理论解释,还附带了完整的开源模型,方便读者进行实践操作和进一步研究。
recommend-type

Pansophica开源项目:智能Web搜索代理的探索

Pansophica开源项目是一个相对较新且具有创新性的智能Web搜索代理,它突破了传统搜索引擎的界限,提供了一种全新的交互方式。首先,我们来探讨“智能Web搜索代理”这一概念。智能Web搜索代理是一个软件程序或服务,它可以根据用户的查询自动执行Web搜索,并尝试根据用户的兴趣、历史搜索记录或其他输入来提供个性化的搜索结果。 Pansophica所代表的不仅仅是搜索结果的展示,它还强调了一个交互式的体验,在动态和交互式虚拟现实中呈现搜索结果。这种呈现方式与现有的搜索体验有着根本的不同。目前的搜索引擎,如Google、Bing和Baidu等,多以静态文本和链接列表的形式展示结果。而Pansophica通过提供一个虚拟现实环境,使得搜索者可以“扭转”视角,进行“飞行”探索,以及“弹网”来浏览不同的内容。这种多维度的交互方式使得信息的浏览变得更加快速和直观,有望改变用户与网络信息互动的方式。 接着,我们关注Pansophica的“开源”属性。所谓开源,指的是软件的源代码可以被公众获取,任何个人或组织都可以自由地使用、学习、修改和分发这些代码。开源软件通常由社区进行开发和维护,这样的模式鼓励了协作创新并减少了重复性劳动,因为全世界的开发者都可以贡献自己的力量。Pansophica项目作为开源软件,意味着其他开发者可以访问和使用其源代码,进一步改进和扩展其功能,甚至可以为Pansophica构建新的应用或服务。 最后,文件名称“Pansophica-src-1.3”表明了我们讨论的特定版本的Pansophica开源代码。数字“1.3”很可能指的是该版本号,表明这是Pansophica项目的第1.3个公开版本。这个版本号对于开发者来说很重要,因为它可以帮助他们追踪更新的进度、比较版本间的差异以及选择最适合其项目的版本。 综合以上信息,Pansophica开源项目不仅在搜索代理领域引入了创新的交互方式,而且通过开源的方式,提供了技术共享和协作的可能性。对于IT行业来说,开源项目是推动技术发展的重要力量,它激发了社区的活力,促进了知识的传播和技术的进步。通过构建一个开源社区,Pansophica可以吸引来自世界各地的开发者和研究人员,共同探索如何将搜索技术与交互式虚拟现实技术结合起来,为用户带来更加丰富和有效的信息检索体验。同时,Pansophica的开源性质也使其更容易适应未来技术的发展,随着社区的不断贡献和反馈,这一平台有望在功能和性能上得到持续的优化和增强。
recommend-type

跨平台内容提取无忧:coze工作流应对社交媒体挑战

# 1. 社交媒体内容提取的必要性与挑战 ## 1.1 内容提取的必要性 在数字时代,社交媒体内容是信息与数据流动的主干道,公司、个人和研究者都在寻求从这些信息中提取价值。无论是为了市场趋势分析、品牌监控、消费者洞察还是公共舆论监测,有效的社交媒体内容提取变得至关重要。这不仅有助于企业做出数据驱动的决策,也能使个人更好地理
recommend-type

vrrp主设备发送的免费arp

<think>好的,我现在需要回答用户关于VRRP主设备发送免费ARP的目的和工作原理的问题。首先,我得回忆一下VRRP的基本概念和工作机制。VRRP,虚拟路由冗余协议,主要用于在局域网中提供默认网关的冗余备份,确保网络的高可用性。主设备(Master)负责转发流量,而备用设备(Backup)则在主设备故障时接管工作。 用户的问题集中在主设备发送免费ARP的目的和机制上。根据引用[2],免费ARP用于通知下游设备虚拟MAC地址的变更。当主设备被选举出来后,它需要让局域网内的其他设备知道虚拟IP对应的MAC地址已经指向自己,这样流量才会被正确路由到主设备。免费ARP的作用应该就是更新这些设备的
recommend-type

为Ghost博客平台打造的Meteor流星包装使用指南

从给定文件信息中,我们可以提炼出以下IT知识点: ### 标题知识点:流星Ghost软件包 1. **流星Ghost软件包的用途**:流星Ghost软件包是专为Ghost博客平台设计的流星(Meteor)应用程序。流星是一个开源的全栈JavaScript平台,用于开发高性能和易于编写的Web应用程序。Ghost是一个开源博客平台,它提供了一个简单且专业的写作环境。 2. **软件包的作用**:流星Ghost软件包允许用户在流星平台上轻松集成Ghost博客。这样做的好处是可以利用流星的实时特性以及易于开发和部署的应用程序框架,同时还能享受到Ghost博客系统的便利和美观。 ### 描述知识点:流星Ghost软件包的使用方法 1. **软件包安装方式**:用户可以通过流星的命令行工具添加名为`mrt:ghost`的软件包。`mrt`是流星的一个命令行工具,用于添加、管理以及配置软件包。 2. **初始化Ghost服务器**:描述中提供了如何在服务器启动时运行Ghost的基本代码示例。这段代码使用了JavaScript的Promise异步操作,`ghost().then(function (ghostServer) {...})`这行代码表示当Ghost服务器初始化完成后,会在Promise的回调函数中提供一个Ghost服务器实例。 3. **配置Ghost博客**:在`then`方法中,首先会获取到Ghost服务器的配置对象`config`,用户可以在此处进行自定义设置,例如修改主题、配置等。 4. **启动Ghost服务器**:在配置完成之后,通过调用`ghostServer.start()`来启动Ghost服务,使其能够处理博客相关的请求。 5. **Web浏览器导航**:一旦流星服务器启动并运行,用户便可以通过Web浏览器访问Ghost博客平台。 ### 标签知识点:JavaScript 1. **JavaScript作为流星Ghost软件包的开发语言**:标签指出流星Ghost软件包是使用JavaScript语言开发的。JavaScript是一种在浏览器端广泛使用的脚本语言,它也是流星平台的基础编程语言。 2. **流星和Ghost共同使用的语言**:JavaScript同样也是Ghost博客平台的开发语言。这表明流星Ghost软件包可以无缝集成,因为底层技术栈相同。 ### 压缩包子文件的文件名称列表知识点:meteor-ghost-master 1. **版本控制和软件包结构**:文件名称`meteor-ghost-master`暗示了该软件包可能托管在像GitHub这样的版本控制系统上。文件名中的`master`通常指的是主分支或主版本。 2. **软件包的目录结构**:通过文件名称可以推断出该软件包可能拥有一个标准的流星软件包结构,包含了初始化、配置、运行等必要的模块和文件。 3. **软件包的维护状态**:由于文件名没有包含特定的版本号,我们无法直接得知软件包的最新更新情况。通常,软件包维护者会将最新的版本代码放在`master`分支上。 ### 总结 流星Ghost软件包提供了一个有效的解决方案,使得流星平台的开发者能够在他们的应用中添加Ghost博客功能。软件包的使用简便,通过流星的命令行工具安装,并通过JavaScript代码配置和启动Ghost服务。通过流星Ghost软件包,开发者能够享受流星的实时特性以及Ghost博客系统的便利性。此外,软件包的命名和结构也暗示了其维护和版本控制的模式,有助于开发者更好地理解如何使用和维护这一软件包。
recommend-type

抖音标题生成自动化:用coze工作流释放创意

# 1. 抖音标题生成自动化的重要性 随着社交媒体平台的崛起,内容的吸引力很大程度上取决于标题的创意与精准性。抖音作为一个日活亿级的短视频平台,高质量的标题能够有效提高视频的点击率,增加内容的传播。但是,人工撰写标题不仅耗时耗力,而且很难做到快速响应热点,自动化标题生成工具应运而生。coze工作流,作为一种实现自动化生成抖音标题的工具,其重要性不言而喻。它能够利用大数据分析和机器学习技术,提高标题的吸引
recommend-type

spss消费结构因子分析

<think>好的,我现在需要帮助用户在SPSS中进行消费结构的因子分析。首先,我要回忆一下因子分析的基本步骤和SPSS的操作流程。用户可能对SPSS不太熟悉,所以步骤要详细,同时需要结合他们提供的引用内容,特别是引用[2]中的适用条件和检验方法。 首先,用户提到了消费结构的数据,这可能包括多个变量,如食品、住房、交通等支出。因子分析适用于这种情况,可以降维并找出潜在因子。根据引用[2],需要检查样本量是否足够,变量间是否有相关性,以及KMO和Bartlett检验的结果。 接下来,我需要按照步骤组织回答:数据准备、适用性检验、因子提取、因子旋转、命名解释、计算得分。每个步骤都要简明扼要,说
recommend-type

OpenMediaVault的Docker映像:快速部署与管理指南

根据提供的文件信息,我们将详细讨论与标题和描述中提及的Docker、OpenMediaVault以及如何部署OpenMediaVault的Docker镜像相关的一系列知识点。 首先,Docker是一个开源的应用容器引擎,允许开发者打包应用及其依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。 OpenMediaVault是一个基于Debian的NAS(网络附加存储)解决方案。它专为家庭或小型办公室提供文件共享、网络附加存储以及打印服务。它提供了一个易用的Web界面,通过这个界面用户可以管理服务器配置、网络设置、用户权限、文件服务等。 在描述中提到了一些Docker命令行操作: 1. `git clone`:用于克隆仓库到本地,这里的仓库指的是“docker-images-openmedivault”。 2. `docker build -t omv`:这是一个构建Docker镜像的命令,其中`-t`参数用于标记镜像名称和标签,这里是标记为“omv”。 3. `docker run`:运行一个容器实例,`-t`参数用于分配一个伪终端,`-i`参数用于交互式操作,`-p 80:80`则是将容器的80端口映射到宿主机的80端口。 启动服务的部分涉及OpenMediaVault的配置和初始化: - ssh服务:用于远程登录到服务器的协议。 - php5-fpm:是PHP的一个FastCGI实现,用于加速PHP的运行。 - nginx:是一个高性能的HTTP和反向代理服务器,常用于优化静态内容的分发。 - openmediavault引擎:指的是OpenMediaVault的核心服务。 - rrdcached:用于收集和缓存性能数据,这些数据可以被rrdtool图形化工具读取。 - collectd:是一个守护进程,用于收集系统性能和提供各种存储方式和传输方式来存储所收集的数据。 为了访问服务,需要在浏览器中输入"http:// IP_OF_DOCKER",其中`IP_OF_DOCKER`指的是运行Docker容器的主机IP地址。 描述中还提到了一个步骤:“在System-> Network-> Interfaces中添加带有dhcp的eth0”,这指的是需要在OpenMediaVault的Web管理界面中配置网络接口。`eth0`是网络接口的名称,通常代表第一个以太网接口。DHCP(动态主机配置协议)是一种自动为网络中的设备分配IP地址的协议,这样设备就可以连接网络并开始通信,无需手动配置IP地址。 【压缩包子文件的文件名称列表】中的“docker-images-openmediavault-master”暗示了这是一个包含Docker镜像文件的代码仓库。通常,“master”分支是代码的主分支,包含了代码库中最新且通常是最稳定的版本。用户可以通过克隆该仓库到本地来获取所有相关的Dockerfile、配置脚本及依赖文件,以便能够自行构建和运行OpenMediaVault的Docker镜像。 综上所述,这些知识点涵盖了从基本的Docker概念、Docker命令行操作、OpenMediaVault服务启动和管理,到具体的网络配置及Docker仓库操作,都是进行Docker化OpenMediaVault部署的关键步骤。
recommend-type

小红书文案提取一步到位:coze工作流操作全攻略

# 1. coze工作流概述 工作流系统是企业信息化和数字化转型的核心组件之一,它通过自动化流程管理提升效率,确保业务流程的顺畅执行。coze工作流作为当前市场上较为先进的工作流解决方案,它不仅仅是一套软件工具,更是一个集成化的平台,旨在通过流程自动化和智能化提升企业运营效率。 coze工作流的引入不仅有助于标准化和优化企业的业务流程,还可以通过可配置的流程设计,满足不同部门的特定需求。在组织的业务流程中
recommend-type

戴尔R630设置来电自动开机

<think>嗯,用户需要设置Dell R630服务器在断电恢复后自动开机。这个问题很实用,特别是对于托管在机房或需要远程管理的服务器来说,电源恢复后自动启动能减少运维压力。 从用户提到的R630型号来看,这是Dell PowerEdge系列的主流机型,设置方法应该和其他同代服务器类似。用户可能遇到意外断电后需要手动开机的情况,这会影响服务可用性。 我回忆Dell服务器的电源管理逻辑:主要有两个关键设置点。首先是BIOS里的电源恢复策略,选项包括"保持关机"、"自动开机"和"恢复断电前状态"。其次是iDRAC远程管理卡里的设置,这个更灵活但需要配置网络。 用户可能不熟悉服务器管理,所以需