肥仔教程网

SEO 优化与 Web 开发技术学习分享平台

PYTHON学习汇报

小白一段时间学习PYTHON后,结合AI的第一个工具:看图工具

PYTHON+PYQT6

欢迎大家交流!

当前主要功能

  • 浏览指定文件夹
  • 过滤图片格式

GUI框架到底是个啥,为什么要学QT

GUI(Graphical User Interface,图形用户界面)是指采用图形的方式显示用户操作的界面,它极大地方便了用户的操作。

1.GUI框架的概念

用Python讓電腦攝像頭實現掃二維碼

import sys  # 系統模組,用來存取命令列參數與系統功能
import cv2  # OpenCV,處理影像與相機操作
import numpy as np  # Numpy,用來處理數值與陣列
from pyzbar import pyzbar  # pyzbar 模組,用來解碼 QR Code 與條碼

# 匯入 PyQt6 所需的元件
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QLabel,
    QPushButton, QTextEdit, QHBoxLayout, QFrame,
    QFileDialog, QSpacerItem, QSizePolicy, QSlider
)
from PyQt6.QtGui import QImage, QPixmap, QPainter, QPen  # 圖像顯示與繪圖工具
from PyQt6.QtCore import Qt, QTimer, QRect  # 基本型別與計時器、矩形框
from PIL import Image, ImageDraw, ImageFont  # PIL 用於繪製 QR Code 邊框與文字


# 主應用程式類別:QRCodeScannerApp,繼承 QWidget
class QRCodeScannerApp(QWidget):
    def __init__(self):
        super().__init__()  # 初始化父類別
        self.setWindowTitle("二維碼掃描器")  # 設定視窗標題
        self.setGeometry(500, 100, 705, 750)  # 設定視窗位置與大小

        # 初始化攝影機與其他功能的變數
        self.camera_index = 0  # 攝影機編號(預設為 0)
        self.capture = None  # OpenCV 攝影機物件
        self.timer = QTimer(self)  # 建立 PyQt 的計時器
        self.timer.timeout.connect(self.update_frame)  # 設定定時器觸發時執行 update_frame()
        self.logged_codes = set()  # 儲存已辨識的 QR code(避免重複顯示)
        self.brightness = 0  # 初始亮度值
        self.contrast = 1.0  # 初始對比度值
        self.manual_image = None  # 手動選擇的圖片
        self.manual_image_decoded = False  # 是否已經辨識過手動圖片

        # 框選辨識的相關參數
        self.selecting = False  # 是否正在框選
        self.selection_start = None  # 框選起始點
        self.selection_end = None  # 框選結束點
        self.selection_rect = None  # 框選矩形

        # 新增反色功能相關變數
        self.invert_enabled = False

        # 新增縮放相關變數
        self.zoom_factor = 1.0
        self.min_zoom = 0.1
        self.max_zoom = 5.0

        self.init_ui()  # 建立使用者介面

    # 建立整個 UI 介面
    def init_ui(self):
        layout = QVBoxLayout()  # 主垂直排列佈局

        # 顯示標題文字
        title_label = QLabel("<h2>二維碼掃描器</h2>")
        title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(title_label)

        # 用於顯示相機畫面的 QLabel
        self.image_label = QLabel()
        self.image_label.setFixedSize(640, 480)  # 固定大小
        self.image_label.setStyleSheet("background-color: black;")  # 初始黑底
        layout.addWidget(self.image_label)

        # 按鈕群組
        button_layout = QHBoxLayout()
        self.start_button = QPushButton("開始掃描")  # 開始按鈕
        self.start_button.clicked.connect(self.start_scanning)  # 綁定點擊事件

        self.stop_button = QPushButton("停止掃描")  # 停止按鈕
        self.stop_button.clicked.connect(self.stop_scanning)
        self.stop_button.setEnabled(False)  # 初始為不可按

        self.clear_log_button = QPushButton("清除日誌")  # 清除日誌
        self.clear_log_button.clicked.connect(self.clear_log)

        self.manual_image_button = QPushButton("手動選擇圖片")  # 手動載入圖片
        self.manual_image_button.clicked.connect(self.select_image)

        self.manual_select_button = QPushButton("框選區域識別")  # 啟動框選辨識
        self.manual_select_button.clicked.connect(self.enable_manual_selection)

        # 新增反色按鈕
        self.invert_button = QPushButton("反色")
        self.invert_button.clicked.connect(self.toggle_invert)
        button_layout.addWidget(self.invert_button)

        # 將所有按鈕加入水平排版
        button_layout.addWidget(self.start_button)
        button_layout.addWidget(self.stop_button)
        button_layout.addWidget(self.clear_log_button)
        button_layout.addWidget(self.manual_image_button)
        button_layout.addWidget(self.manual_select_button)
        layout.addLayout(button_layout)

        # 亮度調整滑桿
        brightness_layout = QHBoxLayout()
        brightness_label = QLabel("亮度")
        self.brightness_slider = QSlider(Qt.Orientation.Horizontal)
        self.brightness_slider.setRange(-100, 100)  # 範圍 -100 ~ 100
        self.brightness_slider.setValue(0)
        self.brightness_slider.valueChanged.connect(self.update_brightness)
        brightness_layout.addWidget(brightness_label)
        brightness_layout.addWidget(self.brightness_slider)
        layout.addLayout(brightness_layout)

        # 對比度調整滑桿
        contrast_layout = QHBoxLayout()
        contrast_label = QLabel("對比度")
        self.contrast_slider = QSlider(Qt.Orientation.Horizontal)
        self.contrast_slider.setRange(10, 300)  # 10% 到 300%
        self.contrast_slider.setValue(100)
        self.contrast_slider.valueChanged.connect(self.update_contrast)
        contrast_layout.addWidget(contrast_label)
        contrast_layout.addWidget(self.contrast_slider)
        layout.addLayout(contrast_layout)

        # 日誌區域(顯示掃描結果)
        layout.addWidget(QLabel("<b>掃描日誌:</b>"))
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        layout.addWidget(self.log_text)

        # 增加空白區域讓排版更好看
        spacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
        layout.addItem(spacer)

        # 加入邊框樣式的外框
        main_frame = QFrame()
        main_frame.setLayout(layout)
        main_frame.setStyleSheet("border: 2px solid #4CAF50; border-radius: 5px; padding: 10px;")

        # 加到主視窗佈局中
        central_layout = QVBoxLayout(self)
        central_layout.addWidget(main_frame)
        self.setLayout(central_layout)

    # 啟動攝影機掃描
    def start_scanning(self):
        self.manual_image = None  # 清除手動載入的圖片
        self.capture = cv2.VideoCapture(self.camera_index)  # 開啟攝影機
        if not self.capture.isOpened():  # 無法打開則顯示錯誤
            self.log_message("無法開啟相機")
            return
        self.timer.start(30)  # 啟動計時器,每 30ms 更新一張畫面
        self.start_button.setEnabled(False)
        self.stop_button.setEnabled(True)
        self.log_message("開始掃描...")

    # 停止攝影機與計時器
    def stop_scanning(self):
        if self.capture and self.capture.isOpened():
            self.capture.release()  # 釋放攝影機資源
        self.timer.stop()
        self.image_label.clear()  # 清除畫面
        self.image_label.setStyleSheet("background-color: black;")
        self.start_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        self.log_message("停止掃描")

    # 每次定時器觸發時執行,更新畫面
    def update_frame(self):
        if self.capture and self.capture.isOpened():
            ret, frame = self.capture.read()
            if ret:
                adjusted_frame = self.adjust_brightness_contrast(frame)
                self.decode_qr_codes(adjusted_frame)
        elif self.manual_image is not None and not self.manual_image_decoded:
            adjusted_image = self.adjust_brightness_contrast(self.manual_image)
            self.decode_qr_codes(adjusted_image)
        elif self.manual_image is not None:
            adjusted_image = self.adjust_brightness_contrast(self.manual_image)
            rgb_image = cv2.cvtColor(adjusted_image, cv2.COLOR_BGR2RGB)
            self.display_image(rgb_image)

    # 依據滑桿設定亮度與對比度,並考慮反色
    def adjust_brightness_contrast(self, frame):
        frame = cv2.convertScaleAbs(frame, alpha=self.contrast, beta=self.brightness)
        if self.invert_enabled:
            frame = cv2.bitwise_not(frame)
        return frame

    # 解碼 QR Code 並畫上邊框與結果
    def decode_qr_codes(self, frame):
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        barcodes = pyzbar.decode(gray)

        if not barcodes:
            rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            self.display_image(rgb_frame)
            return

        self.manual_image_decoded = True
        img_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(img_pil)

        try:
            font = ImageFont.truetype("arial.ttf", 20)
        except IOError:
            font = ImageFont.load_default()

        for barcode in barcodes:
            barcode_data = barcode.data.decode("utf-8")
            barcode_type = barcode.type

            # 加入日誌(避免重複顯示)
            if barcode_data not in self.logged_codes:
                self.log_message(f"掃描到條碼: {barcode_data} ({barcode_type})")
                self.logged_codes.add(barcode_data)

            # 使用 polygon 畫出旋轉後的 QR code 邊框
            points = barcode.polygon
            if len(points) > 4:
                hull = cv2.convexHull(np.array([(p.x, p.y) for p in points])).squeeze()
                polygon = [(int(x), int(y)) for [x, y] in hull]
            else:
                polygon = [(point.x, point.y) for point in points]

            if len(polygon) > 1:
                draw.line(polygon + [polygon[0]], fill=(255, 0, 0), width=3)

            # 顯示 QR code 資料文字在左上角或第一個點
            if polygon:
                text_position = (polygon[0][0], polygon[0][1] - 25)
                draw.text(text_position, barcode_data, font=font, fill=(0, 255, 0))

        result_frame = cv2.cvtColor(np.asarray(img_pil), cv2.COLOR_RGB2BGR)
        rgb_frame = cv2.cvtColor(result_frame, cv2.COLOR_BGR2RGB)
        self.display_image(rgb_frame)

    # 顯示影像於 image_label
    def display_image(self, rgb_frame):
        # 應用縮放
        if self.zoom_factor != 1.0:
            h, w = rgb_frame.shape[:2]
            new_h = int(h * self.zoom_factor)
            new_w = int(w * self.zoom_factor)
            rgb_frame = cv2.resize(rgb_frame, (new_w, new_h), interpolation=cv2.INTER_LINEAR)

        self.current_display_image = rgb_frame.copy()
        h, w, ch = rgb_frame.shape
        bytes_per_line = ch * w
        qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format.Format_RGB888)
        pixmap = QPixmap.fromImage(qt_image)

        # 若有框選,畫出紅框
        if self.selection_rect:
            painter = QPainter(pixmap)
            pen = QPen(Qt.GlobalColor.red, 2, Qt.PenStyle.DashLine)
            painter.setPen(pen)
            painter.drawRect(self.selection_rect)
            painter.end()

        scaled_pixmap = pixmap.scaled(self.image_label.size(), Qt.AspectRatioMode.KeepAspectRatio)
        self.image_label.setPixmap(scaled_pixmap)

    # 顯示訊息到日誌
    def log_message(self, message):
        self.log_text.append(message)
        self.log_text.ensureCursorVisible()

    # 清除日誌內容並重置已辨識條碼集合
    def clear_log(self):
        self.log_text.clear()
        self.logged_codes.clear()

    # 當視窗關閉時,自動釋放攝影機資源
    def closeEvent(self, event):
        self.stop_scanning()  # 停止掃描與釋放攝影機
        event.accept()  # 接受關閉事件

    # 讓使用者手動選擇一張圖片
    def select_image(self):
        self.stop_scanning()  # 若正在掃描則先停止
        file_dialog = QFileDialog(self)
        file_dialog.setNameFilter("Image Files (*.png *.jpg *.bmp)")  # 限定檔案類型
        file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
        if file_dialog.exec():  # 若選擇成功
            image_path = file_dialog.selectedFiles()[0]
            self.load_manual_image(image_path)

    # 載入並顯示手動選擇的圖片
    def load_manual_image(self, image_path):
        image = cv2.imread(image_path)  # 使用 OpenCV 讀取圖片
        if image is None:  # 圖片載入失敗
            self.log_message("無法打開圖片")
            self.manual_image = None
            self.image_label.clear()
            self.image_label.setStyleSheet("background-color: black;")
            return
        # 成功載入
        self.manual_image = image
        self.manual_image_decoded = False  # 尚未辨識
        self.selection_rect = None  # 清除框選區域
        self.zoom_factor = 1.0  # 重置縮放比例
        adjusted_image = self.adjust_brightness_contrast(self.manual_image)
        rgb_image = cv2.cvtColor(adjusted_image, cv2.COLOR_BGR2RGB)
        self.display_image(rgb_image)
        self.log_message("已載入手動選擇的圖片")

    # 使用者調整亮度滑桿時觸發
    def update_brightness(self, value):
        self.brightness = value
        self.refresh_manual_image()  # 重新顯示圖片

    # 使用者調整對比度滑桿時觸發
    def update_contrast(self, value):
        self.contrast = value / 100.0  # 轉換為 0.1 ~ 3.0 間的比例
        self.refresh_manual_image()

    # 重新顯示已載入的手動圖片(依據亮度/對比度)
    def refresh_manual_image(self):
        if self.capture and self.capture.isOpened():
            return  # 若攝影機仍開啟,不處理
        elif self.manual_image is not None and not self.manual_image_decoded:
            adjusted_image = self.adjust_brightness_contrast(self.manual_image)
            self.decode_qr_codes(adjusted_image)
        elif self.manual_image is not None:
            adjusted_image = self.adjust_brightness_contrast(self.manual_image)
            rgb_image = cv2.cvtColor(adjusted_image, cv2.COLOR_BGR2RGB)
            self.display_image(rgb_image)

    # 啟用框選辨識模式
    def enable_manual_selection(self):
        if self.manual_image is not None:
            self.selecting = True
            self.selection_rect = None
            self.log_message("請用滑鼠框選區域進行識別 (按 Esc 可取消)")

    # 滑鼠按下事件 - 開始框選
    def mousePressEvent(self, event):
        if self.selecting and event.button() == Qt.MouseButton.LeftButton:
            self.selection_start = event.position().toPoint()  # 記錄起點

    # 滑鼠移動事件 - 更新選取框
    def mouseMoveEvent(self, event):
        if self.selecting and self.selection_start:
            self.selection_end = event.position().toPoint()  # 記錄終點
            self.selection_rect = QRect(self.selection_start, self.selection_end)  # 建立矩形
            self.display_image(self.current_display_image)  # 顯示帶框的畫面

    # 滑鼠放開事件 - 完成框選並執行辨識
    def mouseReleaseEvent(self, event):
        if self.selecting and event.button() == Qt.MouseButton.LeftButton and self.selection_rect:
            self.process_selected_region()  # 處理選取區域
            self.selection_start = None
            self.selection_end = None
            self.selection_rect = None  # 清除框選框(保持模式)
            self.display_image(self.current_display_image)

    # 按下鍵盤 Esc 取消框選模式
    def keyPressEvent(self, event):
        if event.key() == Qt.Key.Key_Escape:
            self.selecting = False
            self.selection_rect = None
            self.display_image(self.current_display_image)
            self.log_message("已取消框選模式")

    # 處理框選的影像區域
    def process_selected_region(self):
        if not self.selection_rect or self.manual_image is None:
            return

        # 計算實際圖片與 QLabel 的比例
        label_size = self.image_label.size()
        img_h, img_w, _ = self.manual_image.shape
        scale_x = img_w / self.image_label.width()
        scale_y = img_h / self.image_label.height()

        # 將框選的畫面轉換為對應圖片中的區域
        x = int(self.selection_rect.x() * scale_x)
        y = int(self.selection_rect.y() * scale_y)
        w = int(self.selection_rect.width() * scale_x)
        h = int(self.selection_rect.height() * scale_y)

        roi = self.manual_image[y:y + h, x:x + w]  # 擷取 ROI 區域
        if roi.size == 0:
            self.log_message("選取區域無效")
            return

        # 框選區域放大
        self.zoom_factor = min(self.max_zoom, max(self.min_zoom, 2.0))  # 固定放大2倍
        adjusted_roi = self.adjust_brightness_contrast(roi)
        self.decode_qr_codes(adjusted_roi)

    # 切換反色功能
    def toggle_invert(self):
        self.invert_enabled = not self.invert_enabled
        if self.invert_enabled:
            self.log_message("反色功能已開啟")
        else:
            self.log_message("反色功能已關閉")
        self.refresh_manual_image()

    # 鼠標滾輪事件 - 放大縮小圖像
    def wheelEvent(self, event):
        if self.manual_image is not None:
            delta = event.angleDelta().y()
            if delta > 0:
                self.zoom_factor = min(self.max_zoom, self.zoom_factor * 1.1)
            else:
                self.zoom_factor = max(self.min_zoom, self.zoom_factor / 1.1)
            self.refresh_manual_image()


# 主程式進入點,啟動應用程式
if __name__ == '__main__':
    app = QApplication(sys.argv)  # 建立 QApplication 物件
    window = QRCodeScannerApp()  # 建立主視窗
    window.show()  # 顯示視窗
    sys.exit(app.exec())  # 執行應用程式主迴圈

PyQt5-3.PyQt入门介绍

1. PyQt5简介

pyqt5是一套Python绑定Digia QT5应用的框架。它可用于Python 2和3。本教程使用Python 3。Qt库是最强大的GUI库之一。pyqt5的官方网站http://www.riverbankcomputing.co.uk/news。

如何使用Python编写文件编辑工具

尝试用使用PyQt6来编写一个文件文件的编辑器,

基于上一篇文件,因为需要对应安装Qt及PyQt6框架才可以正常运行,安装PyQt6的步骤如下:

一文全面了解:如何使用Python的界面框架 PyQt 构建 GUI

如何使用 PyQt 构建 GUI

<> 14 分钟阅读

使用 Python 构建用户界面 - PyQt 和 Tkinter 比较

您是否厌倦了您创建的所有在控制台中运行的程序?尝试图形界面!这在 Python 中很容易做到。

PyQt 还是 Tkinter?

AI-ClothingTryOn:开源的AI虚拟试穿项目,可生成多版本试衣效果

Al-ClothingTryOn是基于GoogleGeminiAI技术实现虚拟试衣功能。AI-ClothingTryOn支持分别上传人物照片和服装照片,基于AI技术生成逼真的合成图像,展示人物穿上所选服装的效果。

AI-ClothingTryOn支持生成多版本试衣效果,用户能自定义AI提示词优化结果。AI-ClothingTryOn适用于普通用户和开发者,提供EXE文件和源码两种安装方式,方便不同用户群体使用。

Python 生成二維碼工具

此代码借助 PyQt6 库开发出一个可视化的精美二维码生成器应用程序。下面将从代码结构、主要功能模块和关键代码逻辑这几个方面进行解读。


代码结构

代码定义了一个名为 QRCodeGenerator 的类,该类继承自 QWidget,属于 PyQt6 中的窗口部件基类。以下是这个类里的主要方法:

开发图形界面Tkinter、wxPython、PyQt、PySide选哪一个?

  学习Python,就不得不提一下Python的图形界面开发,如果只用命令行写程序,用户体验不太好,难以胜任复杂的人机交互场景。Python的图形界面开发库常用的有:Tkinter、wxPython、PyQt、PySide等等。当然还有其他比较优秀的GUI框架,相对来说用的人比较少。这里就上述几个常用的GUI框架展开讨论。

<< 1 2 > >>
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言