当前位置:首页>python>《【码海听潮原创】Python调用PaddleOCR + Qwen双模型实战:批量提取PDF/图片公章并智能抠图,从此告别PS手动抠图!》

《【码海听潮原创】Python调用PaddleOCR + Qwen双模型实战:批量提取PDF/图片公章并智能抠图,从此告别PS手动抠图!》

  • 2026-10-11 10:59:09
《【码海听潮原创】Python调用PaddleOCR + Qwen双模型实战:批量提取PDF/图片公章并智能抠图,从此告别PS手动抠图!》

“带你横跨办公自动化的数据江海”

@摸鱼

闻道有先后,术业有专攻。各位大佬们大家好!~我是你们的老朋友摸鱼~,本人在十多年的日常工作中摸爬滚打攒了不少Python办公自动化的实用项目技巧,自创立"码海听潮"公众号以来,已经陆续分享70多篇原创文章啦!里面满满的办公实操干货,希望能与各位大佬共同探讨办公效率提升之道,实现不加班自由。

好叻,多了不说,少了不唠,咱直接上干货。

办公需求场景

从崩溃到优雅的进化

有一个神秘的PDF文件夹,里面每个PDF文档里都有公章,现在的需求提取所有PDF公章并且对公章进行抠图, 要是这种类似的需求你的Big Boss安排你去完成,请问阁下该如何应对?

需求的PDF文件夹和PDF文档如下图:

  • 需求PDF文件夹

  • PDF文档

办公痛点分析

01

 痛点1:重复劳动多,效率极低

    • 重复性劳动: 假设有几百个PDF,每个文件都需要经历“打开PDF -> 找到公章位置 -> 放大图像 -> 选择抠图工具 -> 抠图 -> 保存”这一整套流程。这种机械化的重复操作极度枯燥且耗时。

    • 无法批量处理: 人工无法同时处理多个文件,只能逐个处理,工作量与文件数量成正比。

    02

     痛点2:公章位置不固定,查找困难

      • 视觉搜索疲劳: 公章可能出现在每一页的页眉、页脚、左下角、中间空白处,或者压在文字上方。人工需要在每一页中“找”章,长时间盯着屏幕寻找特定图案,极易造成视觉疲劳和遗漏。

      • 多页文档干扰: 如果PDF页数很多(例如数十页),但只有最后一页有章,人工也需要翻到最后才能找到,前面的翻页时间都是无效浪费。

      03

      痛点3:抠图精度难以把控

      • 边缘处理难题: 公章通常是红色圆形,带有锯齿状边缘或压住了背景文字。人工用魔棒工具容易残留白色像素或文字碎片;用钢笔工具描边虽然精细,但速度极慢。要做到边缘光滑且不带背景色,对手工操作的要求很高。

      • 清晰度问题: 有些PDF中的公章可能是扫描件,分辨率较低或带有杂点。人工去杂点、修复边缘需要较高的PS技巧,一般人很难处理好。

      由此可见若操作成百上千个PDF文档的话整个操作流程繁琐且耗时,高频次的鼠标点击和键盘输入使操作者手指疲劳,堪称"键盘敲冒烟"式的体力劳动,加上人工疲劳操作极易导致遗漏文件夹。于是乎这时候,按以往的 “解题套路”,Python 的专属 BGM 该响起来了 ——go~ go~ go~,救苦救难的大救星这不就来了!!

      @摸鱼

      问题拆解思路

      1.遍历PDF文件夹→

      2.调用Paddleocr-Vl-1.5大模型提取PDF文档里的公章保存为图片→

      3.调用阿里qwen-image大模型对公章图片进行抠图→

      4.保存扣图后的公章图片

      下面,我就用python代码让excel见识一下,什么叫"传统文化遇上赛博效率"(仅展示部分代码,非完整代码,需完整代码看文章末尾说明~)

      import sysimport jsonimport osfrom pathlib import Pathimport subprocessfrom PyQt6.QtWidgets import (    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,    QPushButton, QLabel, QFileDialog, QTextEdit, QProgressBar,    QGroupBox, QMessageBox, QLineEdit, QGridLayout, QTabWidget,    QCheckBox, QSpinBox, QDoubleSpinBox)from PyQt6.QtCore import Qt, QThread, pyqtSignalfrom PyQt6.QtGui import QTextCursor# --- 配置文件路径 ---CONFIG_FILE = "api_config.json"# --- 配置管理函数 ---def load_config():    """加载配置文件"""    default_config = {        "qwen_api_key": "",        "paddle_api_url": "",        "paddle_token": "",        "output_dir": "",        "last_folder": "",        "red_threshold": 0.1,        "extract_seal_only": True    }    if os.path.exists(CONFIG_FILE):        try:            with open(CONFIG_FILE, 'r', encoding='utf-8') as f:                config = json.load(f)                # 确保所有必要的键都存在                for key in default_config:                    if key not in config:                        config[key] = default_config[key]                return config        except Exception as e:            print(f"读取配置文件失败: {str(e)},使用默认配置")            return default_config    else:        return default_configdef save_config(config):    """保存配置文件"""    try:        with open(CONFIG_FILE, 'w', encoding='utf-8') as f:            json.dump(config, f, ensure_ascii=False, indent=2)        return True    except Exception as e:        print(f"保存配置文件失败: {str(e)}")        return False# --- 抽象工作线程基类 ---class BaseWorker(QThread):    """工作线程基类"""    progress_signal = pyqtSignal(str)    progress_value_signal = pyqtSignal(int)    result_signal = pyqtSignal(dict)    error_signal = pyqtSignal(str)    def __init__(self):        super().__init__()        self.running = True    def stop(self):        """停止线程"""        self.running = Falseclass QwenImageWorker(BaseWorker):    """Qwen Image Edit 工作线程 - 接口定义"""    def __init__(self, image_paths, api_key, prompt_text):        super().__init__()        self.image_paths = image_paths        self.api_key = api_key        self.prompt_text = prompt_text    def run(self):        """线程执行函数"""        total_images = len(self.image_paths)        for idx, image_path in enumerate(self.image_paths):            if not self.running:                break            # 模拟处理过程            self.progress_value_signal.emit(int((idx / total_images) * 100))            self.progress_signal.emit(f"正在处理第 {idx + 1}/{total_images} 张图片: {os.path.basename(image_path)}")            # 模拟API调用延时            self.msleep(500)            # 模拟结果            self.result_signal.emit({                'success': True,                'original_file': image_path,                'processed_files': [f"output/{os.path.basename(image_path)}"],                'message': f"成功处理: {os.path.basename(image_path)}"            })        self.progress_value_signal.emit(100)        self.progress_signal.emit("处理完成!")class PaddleOCRWorker(BaseWorker):    """PaddleOCR-VL-1.5 工作线程 - 接口定义"""    def __init__(self, file_paths, api_url, token, input_base_dir, output_base_dir,                  extract_seal_only=True, red_threshold=0.1):        super().__init__()        self.file_paths = file_paths        self.api_url = api_url        self.token = token        self.input_base_dir = input_base_dir        self.output_base_dir = output_base_dir        self.extract_seal_only = extract_seal_only        self.red_threshold = red_threshold    def run(self):        """线程执行函数 - 需要在实际应用中实现"""        total_files = len(self.file_paths)        for idx, file_path in enumerate(self.file_paths):            if not self.running:                break            # 模拟处理过程            self.progress_value_signal.emit(int((idx / total_files) * 100))            self.progress_signal.emit(f"处理文件 {idx + 1}/{total_files}: {os.path.basename(file_path)}")            # 模拟处理延时            self.msleep(300)            # 模拟结果            self.result_signal.emit({                'success': True,                'file': file_path,                'message': f"成功处理: {os.path.basename(file_path)}"            })        self.progress_value_signal.emit(100)        self.progress_signal.emit(f"批量处理完成!成功: {total_files}, 失败: 0")# --- 主界面类 ---class ImageProcessingApp(QMainWindow):    """图片处理应用主窗口 - 界面框架"""    def __init__(self):        super().__init__()        self.worker = None        # 加载配置        self.config = load_config()        self.qwen_api_key = self.config.get("qwen_api_key", "")        self.paddle_api_url = self.config.get("paddle_api_url", "")        self.paddle_token = self.config.get("paddle_token", "")        self.init_ui()    def init_ui(self):        """初始化UI"""        self.setWindowTitle("图片处理工具 - 界面框架 (欢迎关注微信公众号:码海听潮)")        self.setGeometry(100, 100, 700, 500)        # 设置样式        self.setStyleSheet("""            QMainWindow {                background-color: #f0f2f5;            }            QTabWidget::pane {                border: 1px solid #d1d9e6;                border-radius: 8px;                background-color: white;            }            QTabBar::tab {                font-size: 13px;                font-weight: bold;                padding: 8px 16px;                margin-right: 2px;                background-color: #e6e9f0;                border: 1px solid #d1d9e6;                border-bottom: none;                border-top-left-radius: 6px;                border-top-right-radius: 6px;            }            QTabBar::tab:selected {                background-color: white;                border-bottom: 2px solid #3498db;            }            QGroupBox {                font-size: 13px;                font-weight: bold;                border: 1px solid #d1d9e6;                border-radius: 6px;                margin-top: 8px;                padding-top: 8px;                background-color: white;            }            QGroupBox::title {                subcontrol-origin: margin;                left: 10px;                padding: 0 5px 0 5px;                color: #2c3e50;            }            QPushButton {                font-size: 13px;                padding: 8px 16px;                border-radius: 5px;                border: none;                font-weight: bold;            }            QPushButton:hover {                opacity: 0.9;            }            QPushButton:disabled {                background-color: #bdc3c7;            }#startButton {                background-color: #2ecc71;                color: white;                font-size: 15px;                padding: 12px 24px;            }#folderButton {                background-color: #3498db;                color: white;                padding: 8px 12px;            }#saveConfigButton {                background-color: #9b59b6;                color: white;                padding: 8px 12px;                min-width: 100px;            }            QLabel {                font-size: 13px;            }            QLineEdit {                font-size: 13px;                padding: 8px;                border: 1px solid #d1d9e6;                border-radius: 5px;                background-color: white;            }            QTextEdit {                border: 1px solid #d1d9e6;                border-radius: 5px;                padding: 6px;                font-size: 12px;                background-color: white;            }            QProgressBar {                border: 1px solid #d1d9e6;                border-radius: 5px;                text-align: center;                background-color: white;                font-weight: bold;                height: 20px;            }            QProgressBar::chunk {                background-color: #3498db;                border-radius: 5px;            }        """)        # 创建中心部件        central_widget = QWidget()        self.setCentralWidget(central_widget)        main_layout = QVBoxLayout(central_widget)        main_layout.setSpacing(10)        main_layout.setContentsMargins(10, 10, 10, 10)        # 创建选项卡        self.tab_widget = QTabWidget()        # 添加两个选项卡        self.tab_widget.addTab(self.create_paddle_tab(), "PaddleOCR 大模型公章提取")        self.tab_widget.addTab(self.create_qwen_tab(), "Qwen-Image 大模型公章抠图")        main_layout.addWidget(self.tab_widget)        # 全局进度条        self.global_progress_bar = QProgressBar()        self.global_progress_bar.setTextVisible(True)        self.global_progress_bar.setFormat("%p%")        self.global_progress_bar.setMaximumHeight(20)        main_layout.addWidget(self.global_progress_bar)        # 全局日志        log_group = QGroupBox("处理日志")        log_group.setMaximumHeight(150)        log_layout = QVBoxLayout()        log_layout.setContentsMargins(8, 8, 8, 8)        self.global_log_text = QTextEdit()        self.global_log_text.setReadOnly(True)        self.global_log_text.setMaximumHeight(120)        log_layout.addWidget(self.global_log_text)        log_group.setLayout(log_layout)        main_layout.addWidget(log_group)        # 底部按钮        bottom_layout = QHBoxLayout()        bottom_layout.addStretch()        self.save_config_button = QPushButton("💾 保存配置")        self.save_config_button.setObjectName("saveConfigButton")        self.save_config_button.clicked.connect(self.save_all_config)        self.clear_log_button = QPushButton("🗑️ 清空日志")        self.clear_log_button.setObjectName("saveConfigButton")        self.clear_log_button.clicked.connect(self.clear_log)        bottom_layout.addWidget(self.save_config_button)        bottom_layout.addWidget(self.clear_log_button)        bottom_layout.addStretch()        main_layout.addLayout(bottom_layout)    def create_paddle_tab(self):        """创建PaddleOCR-VL-1.5选项卡"""        tab = QWidget()        layout = QVBoxLayout(tab)        layout.setSpacing(10)        layout.setContentsMargins(10, 10, 10, 10)        # API设置组        api_group = QGroupBox("API设置")        api_layout = QGridLayout()        api_layout.setSpacing(8)        api_layout.setContentsMargins(8, 8, 8, 8)        # API URL        api_url_label = QLabel("API URL:")        self.paddle_api_url_edit = QLineEdit()        self.paddle_api_url_edit.setText(self.paddle_api_url)        self.paddle_api_url_edit.setPlaceholderText("请输入PaddleOCR API URL")        # Token        token_label = QLabel("Token:")        self.paddle_token_edit = QLineEdit()        self.paddle_token_edit.setText(self.paddle_token)        self.paddle_token_edit.setPlaceholderText("请输入Token")        # 红色检测阈值        threshold_label = QLabel("红色阈值:")        self.red_threshold_spin = QDoubleSpinBox()        self.red_threshold_spin.setRange(0.01, 0.5)        self.red_threshold_spin.setSingleStep(0.01)        self.red_threshold_spin.setValue(self.config.get("red_threshold", 0.1))        self.red_threshold_spin.setDecimals(2)        self.red_threshold_spin.setSuffix(" (10%)")        self.red_threshold_spin.setMaximumWidth(120)        # 只提取公章选项        self.extract_seal_only_check = QCheckBox("只提取红色公章")        self.extract_seal_only_check.setChecked(self.config.get("extract_seal_only", True))        api_layout.addWidget(api_url_label, 0, 0)        api_layout.addWidget(self.paddle_api_url_edit, 0, 1, 1, 2)        api_layout.addWidget(token_label, 1, 0)        api_layout.addWidget(self.paddle_token_edit, 1, 1, 1, 2)        api_layout.addWidget(threshold_label, 2, 0)        api_layout.addWidget(self.red_threshold_spin, 2, 1)        api_layout.addWidget(self.extract_seal_only_check, 2, 2)        api_group.setLayout(api_layout)        layout.addWidget(api_group)        # 文件管理组        file_group = QGroupBox("文件管理")        file_layout = QGridLayout()        file_layout.setSpacing(8)        file_layout.setContentsMargins(8, 8, 8, 8)        # 文件夹路径输入框        folder_label = QLabel("文件夹:")        self.paddle_folder_edit = QLineEdit()        self.paddle_folder_edit.setPlaceholderText("选择或输入文件夹路径")        self.paddle_folder_edit.setText(self.config.get("last_folder", ""))        # 文件夹选择按钮        self.paddle_folder_button = QPushButton("浏览")        self.paddle_folder_button.setObjectName("folderButton")        self.paddle_folder_button.clicked.connect(self.select_paddle_folder)        self.paddle_folder_button.setMaximumWidth(60)        # 输出目录        output_label = QLabel("输出:")        self.paddle_output_edit = QLineEdit()        self.paddle_output_edit.setText(self.config.get("output_dir", "paddle_output"))        self.paddle_output_edit.setPlaceholderText("输出目录")        # 输出目录浏览按钮        self.paddle_output_button = QPushButton("浏览")        self.paddle_output_button.setObjectName("folderButton")        self.paddle_output_button.clicked.connect(self.select_paddle_output_folder)        self.paddle_output_button.setMaximumWidth(60)        file_layout.addWidget(folder_label, 0, 0)        file_layout.addWidget(self.paddle_folder_edit, 0, 1)        file_layout.addWidget(self.paddle_folder_button, 0, 2)        file_layout.addWidget(output_label, 1, 0)        file_layout.addWidget(self.paddle_output_edit, 1, 1)        file_layout.addWidget(self.paddle_output_button, 1, 2)        file_group.setLayout(file_layout)        layout.addWidget(file_group)        # 开始按钮        self.paddle_start_button = QPushButton("🚀 开始公章提取")        self.paddle_start_button.setObjectName("startButton")        self.paddle_start_button.clicked.connect(self.start_paddle_processing)        layout.addWidget(self.paddle_start_button)        layout.addStretch()        return tab    def create_qwen_tab(self):        """创建Qwen-Image选项卡"""        tab = QWidget()        layout = QVBoxLayout(tab)        layout.setSpacing(10)        layout.setContentsMargins(10, 10, 10, 10)        # API设置组        api_group = QGroupBox("API设置")        api_layout = QHBoxLayout()        api_layout.setSpacing(8)        api_layout.setContentsMargins(8, 8, 8, 8)        api_key_label = QLabel("API Key:")        self.qwen_api_key_edit = QLineEdit()        self.qwen_api_key_edit.setText(self.qwen_api_key)        self.qwen_api_key_edit.setPlaceholderText("请输入Qwen-Image API Key")        api_layout.addWidget(api_key_label)        api_layout.addWidget(self.qwen_api_key_edit)        api_group.setLayout(api_layout)        layout.addWidget(api_group)        # 图片管理组        image_group = QGroupBox("图片管理")        image_layout = QGridLayout()        image_layout.setSpacing(8)        image_layout.setContentsMargins(8, 8, 8, 8)        # 文件夹路径输入框        folder_label = QLabel("文件夹:")        self.qwen_folder_edit = QLineEdit()        self.qwen_folder_edit.setPlaceholderText("选择或输入图片文件夹路径")        self.qwen_folder_edit.setText(self.config.get("last_folder", ""))        # 文件夹选择按钮        self.qwen_folder_button = QPushButton("浏览")        self.qwen_folder_button.setObjectName("folderButton")        self.qwen_folder_button.clicked.connect(self.select_qwen_folder)        self.qwen_folder_button.setMaximumWidth(60)        # 输出目录        output_label = QLabel("输出:")        self.qwen_output_edit = QLineEdit()        self.qwen_output_edit.setText(self.config.get("output_dir", "qwen_output"))        self.qwen_output_edit.setPlaceholderText("输出目录")        # 输出目录浏览按钮        self.qwen_output_button = QPushButton("浏览")        self.qwen_output_button.setObjectName("folderButton")        self.qwen_output_button.clicked.connect(self.select_qwen_output_folder)        self.qwen_output_button.setMaximumWidth(60)        image_layout.addWidget(folder_label, 0, 0)        image_layout.addWidget(self.qwen_folder_edit, 0, 1)        image_layout.addWidget(self.qwen_folder_button, 0, 2)        image_layout.addWidget(output_label, 1, 0)        image_layout.addWidget(self.qwen_output_edit, 1, 1)        image_layout.addWidget(self.qwen_output_button, 1, 2)        image_group.setLayout(image_layout)        layout.addWidget(image_group)        # 提示词设置组        prompt_group = QGroupBox("提示词")        prompt_layout = QVBoxLayout()        prompt_layout.setSpacing(5)        prompt_layout.setContentsMargins(8, 8, 8, 8)        self.prompt_edit = QTextEdit()        default_prompt = "请仔细识别图片中的公章,确保:\n1. 对公章进行抠图\n2. 保持公章在原图中的实际尺寸\n3. 去除公章下方或周围的黑色文字,使公章背景干净透明\n4. 保持公章颜色与原图完全一致\n5. 保留公章的所有红色字体"        self.prompt_edit.setText(default_prompt)        self.prompt_edit.setMinimumHeight(100)        self.prompt_edit.setMaximumHeight(120)        prompt_layout.addWidget(self.prompt_edit)        prompt_group.setLayout(prompt_layout)        layout.addWidget(prompt_group)        # 开始按钮        self.qwen_start_button = QPushButton("🚀 开始公章抠图")        self.qwen_start_button.setObjectName("startButton")        self.qwen_start_button.clicked.connect(self.start_qwen_processing)        layout.addWidget(self.qwen_start_button)        layout.addStretch()        return tab    # ==================== 文件夹选择函数 ====================    def select_paddle_output_folder(self):        """选择PaddleOCR输出目录"""        current_path = self.paddle_output_edit.text().strip()        if not os.path.exists(current_path):            current_path = os.path.dirname(self.paddle_folder_edit.text().strip()) if self.paddle_folder_edit.text().strip() else ""        folder = QFileDialog.getExistingDirectory(            self,            "选择输出目录",            current_path        )        if folder:            self.paddle_output_edit.setText(folder)            self.config["output_dir"] = folder    def select_qwen_output_folder(self):        """选择Qwen输出目录"""        current_path = self.qwen_output_edit.text().strip()        if not os.path.exists(current_path):            current_path = os.path.dirname(self.qwen_folder_edit.text().strip()) if self.qwen_folder_edit.text().strip() else ""        folder = QFileDialog.getExistingDirectory(            self,            "选择输出目录",            current_path        )        if folder:            self.qwen_output_edit.setText(folder)            self.config["output_dir"] = folder    def select_paddle_folder(self):        """选择PaddleOCR处理文件夹"""        current_path = self.paddle_folder_edit.text().strip()        if not os.path.exists(current_path):            current_path = self.config.get("last_folder", "")        folder = QFileDialog.getExistingDirectory(            self,            "选择文件夹 (包含PDF或图片)",            current_path        )        if folder:            self.paddle_folder_edit.setText(folder)            self.config["last_folder"] = folder    def select_qwen_folder(self):        """选择Qwen处理文件夹"""        current_path = self.qwen_folder_edit.text().strip()        if not os.path.exists(current_path):            current_path = self.config.get("last_folder", "")        folder = QFileDialog.getExistingDirectory(            self,            "选择图片文件夹",            current_path        )        if folder:            self.qwen_folder_edit.setText(folder)            self.config["last_folder"] = folder    # ==================== 处理函数 ====================    def start_paddle_processing(self):        """开始PaddleOCR处理 - 接口方法,需要在实际应用中重写"""        folder = self.paddle_folder_edit.text().strip()        if not folder or not os.path.exists(folder):            QMessageBox.warning(self, "警告", "请选择有效的文件夹!")            return        api_url = self.paddle_api_url_edit.text().strip()        if not api_url:            QMessageBox.warning(self, "警告", "请输入API URL!")            return        token = self.paddle_token_edit.text().strip()        if not token:            QMessageBox.warning(self, "警告", "请输入Token!")            return        output_dir = self.paddle_output_edit.text().strip()        if not output_dir:            output_dir = "paddle_output"        os.makedirs(output_dir, exist_ok=True)        # 收集文件        extensions = ['.pdf', '.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.gif']        file_paths = []        for ext in extensions:            file_paths.extend([str(p) for p in Path(folder).glob(f'**/*{ext}')])        if not file_paths:            QMessageBox.warning(self, "警告", "文件夹中没有找到支持的PDF或图片文件!")            return        # 禁用按钮        self.paddle_start_button.setEnabled(False)        self.save_config_button.setEnabled(False)        # 重置进度条        self.global_progress_bar.setMaximum(100)        self.global_progress_bar.setValue(0)        # 获取设置        extract_seal_only = self.extract_seal_only_check.isChecked()        red_threshold = self.red_threshold_spin.value()        # 创建并启动工作线程        self.worker = PaddleOCRWorker(            file_paths,            api_url,            token,            folder,            output_dir,            extract_seal_only,            red_threshold        )        # 连接信号        self.worker.progress_signal.connect(self.log_message)        self.worker.progress_value_signal.connect(self.global_progress_bar.setValue)        self.worker.result_signal.connect(self.on_paddle_result)        self.worker.error_signal.connect(self.log_message)        self.worker.finished.connect(self.on_worker_finished)        # 开始处理        self.log_message(f"开始PaddleOCR处理,共 {len(file_paths)} 个文件...")        self.log_message(f"提取公章模式: {'启用'if extract_seal_only else'禁用'}")        self.log_message(f"红色检测阈值: {red_threshold}")        self.worker.start()    def start_qwen_processing(self):        """开始Qwen处理 - 接口方法,需要在实际应用中重写"""        folder = self.qwen_folder_edit.text().strip()        if not folder or not os.path.exists(folder):            QMessageBox.warning(self, "警告", "请选择有效的图片文件夹!")            return        api_key = self.qwen_api_key_edit.text().strip()        if not api_key:            QMessageBox.warning(self, "警告", "请输入API Key!")            return        prompt = self.prompt_edit.toPlainText().strip()        if not prompt:            QMessageBox.warning(self, "警告", "请输入提示词!")            return        output_dir = self.qwen_output_edit.text().strip()        if not output_dir:            output_dir = "qwen_output"        os.makedirs(output_dir, exist_ok=True)        self.config["output_dir"] = output_dir        # 收集图片        extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.gif']        image_paths = []        for ext in extensions:            image_paths.extend([str(p) for p in Path(folder).glob(f'**/*{ext}')])            image_paths.extend([str(p) for p in Path(folder).glob(f'**/*{ext.upper()}')])        if not image_paths:            QMessageBox.warning(self, "警告", "文件夹中没有找到支持的图片文件!")            return        # 禁用按钮        self.qwen_start_button.setEnabled(False)        self.save_config_button.setEnabled(False)        # 重置进度条        self.global_progress_bar.setMaximum(100)        self.global_progress_bar.setValue(0)        # 创建并启动工作线程        self.worker = QwenImageWorker(            image_paths,            api_key,            prompt        )        # 连接信号        self.worker.progress_signal.connect(self.log_message)        self.worker.progress_value_signal.connect(self.global_progress_bar.setValue)        self.worker.result_signal.connect(self.on_qwen_result)        self.worker.error_signal.connect(self.log_message)        self.worker.finished.connect(self.on_worker_finished)        # 开始处理        self.log_message(f"开始Qwen处理,共 {len(image_paths)} 张图片...")        self.worker.start()    def on_paddle_result(self, result):        """处理PaddleOCR结果"""        if result['success']:            self.log_message(f"✓ {result['message']}")        else:            self.log_message(f"✗ {result['message']}")    def on_qwen_result(self, result):        """处理Qwen结果"""        if result['success']:            self.log_message(f"✓ {result['message']}")        else:            self.log_message(f"✗ {result['message']}")    def on_worker_finished(self):        """工作线程完成"""        self.paddle_start_button.setEnabled(True)        self.qwen_start_button.setEnabled(True)        self.save_config_button.setEnabled(True)        self.log_message("所有处理完成!")        # 询问是否打开结果文件夹        current_tab = self.tab_widget.currentIndex()        if current_tab == 0:            output_dir = self.paddle_output_edit.text().strip()        else:            output_dir = self.qwen_output_edit.text().strip()        if os.path.exists(output_dir):            reply = QMessageBox.question(                self,                "处理完成",                "处理完成!是否打开结果文件夹?",                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No            )            if reply == QMessageBox.StandardButton.Yes:                try:                    if sys.platform == "win32":                        os.startfile(output_dir)                    elif sys.platform == "darwin":                        subprocess.run(["open", output_dir])                    else:                        subprocess.run(["xdg-open", output_dir])                except Exception as e:                    self.log_message(f"无法打开文件夹: {str(e)}")    def log_message(self, message):        """添加日志消息"""        import time        timestamp = time.strftime("%H:%M:%S", time.localtime())        self.global_log_text.append(f"[{timestamp}] {message}")        cursor = self.global_log_text.textCursor()        cursor.movePosition(QTextCursor.MoveOperation.End)        self.global_log_text.setTextCursor(cursor)    def clear_log(self):        """清空日志"""        self.global_log_text.clear()    def save_all_config(self):        """保存所有配置"""        self.config["qwen_api_key"] = self.qwen_api_key_edit.text().strip()        self.config["paddle_api_url"] = self.paddle_api_url_edit.text().strip()        self.config["paddle_token"] = self.paddle_token_edit.text().strip()        self.config["output_dir"] = self.paddle_output_edit.text().strip() or self.qwen_output_edit.text().strip()        self.config["last_folder"] = self.paddle_folder_edit.text().strip() or self.qwen_folder_edit.text().strip()        self.config["red_threshold"] = self.red_threshold_spin.value()        self.config["extract_seal_only"] = self.extract_seal_only_check.isChecked()        if save_config(self.config):            QMessageBox.information(self, "成功", "所有配置已保存!")            self.log_message("配置已保存到文件")        else:            QMessageBox.warning(self, "警告", "保存配置失败")    def closeEvent(self, event):        """关闭事件处理"""        self.save_all_config()        if self.worker and self.worker.isRunning():            reply = QMessageBox.question(                self,                "确认退出",                "处理正在进行中,确定要退出吗?",                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No            )            if reply == QMessageBox.StandardButton.Yes:                self.worker.stop()                self.worker.wait()                event.accept()            else:                event.ignore()        else:            event.accept()# --- 主程序入口 ---def main():    app = QApplication(sys.argv)    app.setStyle('Fusion')    window = ImageProcessingApp()    window.show()    sys.exit(app.exec())if __name__ == "__main__":    main()

      最终将所有的PDF文档里的公章都提取并抠图完成,实现了之前既定的需求(结果图片已做模糊化处理)...

      通过上面Python自动化脚本,仅用几分钟的时间就完成原需手动操作数小时的工作任务。从最初准备手动人工机械操作的麻木到用python实现高效自动化的畅快,工作效率获得指数级提升,终于实现了不加班熬夜的自由!

      大佬们也可以举一反三,参照上面的代码思路根据自己工作中的实际情况来具体问题具体分析,实现自己定制化的需求。

      结语

      当Python遇见办公,牛马打工人终于笑出了猪叫声

      【职场人必看】每天早上一睁眼,想到又要面对:

      1.📊 堆积如山的Excel表格

      2.📑 机械重复的复制粘贴

      3.✍️ 永远改不完的各类文档

      4.诸如此类的更多........

      是不是连Ctrl+Alt+Delete的心都有了?

      别慌!别急,摸鱼这位“职场外挂”已经带着Python代码来拯救你了!

      友情提示:考虑到没有python环境的朋友需要打包好的成品exe,摸鱼早已贴心打包好,本篇文章代码打包的exe截图如下:

      另外,《码海听潮》公众号所有文章码和exe程序已打包好上传绿联nas私有云,有需要的大佬扫一扫上面博主的个人微信二维码,需要的大佬需支付9.9元永久拥有公众号资源(写原创干货费时费力,属实不易),邀请您进入社区群获取下载链接!!,群内提供python办公自动化交流问题,解决问题,且码海听潮微信公众号文章发布会第一时间会更新到群里,非诚勿扰哈!

      码海听潮官方社区群如下:

      赶紧微信扫一扫下方二维码添加摸鱼君微信

      最新文章

      随机文章