首页 > 其他分享 >使用OpenCV和PyQT编写的图片显示器,实现图片选择显示和保存

使用OpenCV和PyQT编写的图片显示器,实现图片选择显示和保存

时间:2024-04-06 14:32:39浏览次数:29  
标签:pixmap bar img start self PyQT OpenCV btn 图片

使用OpenCV和PyQT编写的图片显示器

简要

为了避免重复编写pyqt控件,先写好此代码,方便使用。实现了以下基础功能:

  • 选择图片
  • 显示图片
  • 保存图片

效果

选择图片:
在这里插入图片描述
显示图片:
在这里插入图片描述
保存图片:
在这里插入图片描述

代码

import copy
import cv2
import sys

from PyQt5.QtWidgets import (
    QApplication,
    QMainWindow,
    QFileDialog,
    QMessageBox,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QFrame,
    QToolButton,
    QSpacerItem,
    QLabel,
    QSizePolicy,
)

from PyQt5.QtGui import (
    QImage,
    QPixmap,
    QIcon,
    QPainter,
    QPen,
)

from PyQt5.QtCore import (
    QSize,
    QRect,
    Qt,
)

from PyQt5 import QtWidgets,QtGui
class ImageLabel(QLabel):
    """"
    用于显示图片的 Label
    """

    def __init__(self, parent=None):
        super().__init__(parent)
        self.x0 = 0
        self.y0 = 0
        self.x1 = 0
        self.y1 = 0
        self.flag = False  # 标记是否能够绘制矩形
        self.__isClear = False  # 标记是否是清除矩形
        self.setAlignment(Qt.AlignCenter)  # 居中对齐
        self.setFrameShape(QtWidgets.QFrame.Box)  # 设置边框
        self.setStyleSheet("border-width: 1px;border-style: solid;border-color: rgb(218, 218, 218)")
        self.setText("")
        self.__w, self.__h = 0, 0
        self.pixmap_width, self.pixmap_height = 0, 0  # pixmap 的宽度、高度
        self.pixmap_x_start, self.pixmap_y_start = 0, 0  # pixmap 在 label 中的起点位置
        self.pixmap_x_end, self.pixmap_y_end = 0, 0  # pixamp 在 label 中的终点位置
        self.img_x_start, self.img_y_start = 0, 0  # 图片中选择的矩形区域的起点位置
        self.img_x_end, self.img_y_end = 0, 0  # 图片中选择的矩形区域的终点位置
        self.autoFillBackground()

    # 鼠标点击事件
    def mousePressEvent(self, event):
        # self.flag = True
        # 鼠标点击,相当于开始绘制矩形,将 isClear 置为 False
        self.__isClear = False
        self.x0 = event.x()
        self.y0 = event.y()
        # 计算 Pixmap 在 Label 中的位置
        self.__w, self.__h = self.width(), self.height()
        self.pixmap_x_start = (self.__w - self.pixmap_width) / 2
        self.pixmap_y_start = (self.__h - self.pixmap_height) / 2
        self.pixmap_x_end = self.pixmap_x_start + self.pixmap_width
        self.pixmap_y_end = self.pixmap_y_start + self.pixmap_height

    # 鼠标释放事件
    def mouseReleaseEvent(self, event):
        # self.flag = False
        self.setCursor(Qt.ArrowCursor)  # 鼠标释放,矩形已经绘制完毕,恢复鼠标样式

    # 鼠标移动事件
    def mouseMoveEvent(self, event):
        if self.flag:
            self.x1 = event.x()
            self.y1 = event.y()
            self.update()

    def setPixmap(self, pixmap):
        super().setPixmap(pixmap)
        self.pixmap_width, self.pixmap_height = pixmap.width(), pixmap.height()

    # 绘制事件
    def paintEvent(self, event):
        super().paintEvent(event)

        # 判断是否是清除
        if self.__isClear:
            return  # 是清除,则不需要执行下面的绘制操作。即此次 paint 事件没有绘制操作,因此界面中没有绘制的图形(从而相当于清除整个界面中已有的图形)

        # 判断用户起始位置是否在图片区域,只有在图片区域才画选择的矩形图
        if (self.pixmap_x_start <= self.x0 <= self.pixmap_x_end) \
                and (self.pixmap_y_start <= self.y0 <= self.pixmap_y_end):
            # 判断结束位置是否在图片区域内,如果超过,则直接设置成图片区域的终点
            if self.x1 > self.pixmap_x_end:
                self.x1 = self.pixmap_x_end
            elif self.x1 < self.pixmap_x_start:
                self.x1 = self.pixmap_x_start

            if self.y1 > self.pixmap_y_end:
                self.y1 = self.pixmap_y_end
            elif self.y1 < self.pixmap_y_start:
                self.y1 = self.pixmap_y_start
            rect = QRect(self.x0, self.y0, self.x1 - self.x0, self.y1 - self.y0)
            painter = QPainter(self)
            painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
            painter.drawRect(rect)
            # 计算矩形区域在图片中的位置
            self.img_x_start = int(self.x0 - self.pixmap_x_start)
            self.img_x_end = int(self.x1 - self.pixmap_x_start)
            self.img_y_start = int(self.y0 - self.pixmap_y_start)
            self.img_y_end = int(self.y1 - self.pixmap_y_start)

    def clearRect(self):
        # 清除
        self.__isClear = True
        self.update()
class MyWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initialize()

    def initialize(self):
        """
        Initialize the user interface and set initial states.
        """
        self.setup_ui()
        self.set_initial_states()

    def setup_ui(self):
        """
        Set up the main window layout and components.
        """
        self.setWindowTitle("Image Editor")
        self.resize(926, 806)

        self.central_widget = QWidget(self)
        self.central_layout = QVBoxLayout(self.central_widget)
        self.setCentralWidget(self.central_widget)

        # Title bar with open, save, and undo buttons
        self.title_bar = QFrame(self.central_widget)
        self.title_bar.setFrameShape(QFrame.StyledPanel)
        self.title_bar.setFrameShadow(QFrame.Raised)
        self.title_layout = QHBoxLayout(self.title_bar)

        self.btn_open = QToolButton(self.title_bar)
        self.btn_save = QToolButton(self.title_bar)
        self.btn_undo = QToolButton(self.title_bar)

        self.title_layout.addWidget(self.btn_open)
        self.title_layout.addWidget(self.btn_save)
        self.title_layout.addWidget(self.btn_undo)

        # Control bar with confirm and cancel buttons
        self.control_bar = QFrame(self.title_bar)
        self.control_layout = QHBoxLayout(self.control_bar)
        self.btn_confirm = QToolButton(self.control_bar)
        self.btn_cancel = QToolButton(self.control_bar)

        self.control_layout.addWidget(self.btn_confirm)
        self.control_layout.addWidget(self.btn_cancel)
        self.title_layout.addWidget(self.control_bar)

        # Add a spacer item to the title layout
        self.title_layout.addItem(QSpacerItem(100, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))

        # Image frame with an ImageLabel widget to display images
        self.img_frame = QFrame(self.central_widget)
        self.img_frame.setFrameShape(QFrame.StyledPanel)
        self.img_frame.setFrameShadow(QFrame.Raised)
        self.img_layout = QHBoxLayout(self.img_frame)
        self.img_display = ImageLabel(self.img_frame)
        self.img_layout.addWidget(self.img_display)

        self.central_layout.addWidget(self.title_bar)
        self.central_layout.addWidget(self.img_frame)

        # Set button text, icons, styles, and layout styles
        self.set_buttons_text_icons()
        self.set_buttons_styles()
        self.set_layout_styles()

    def set_initial_states(self):
        """
        Set initial states for UI components.
        """
        self.control_bar.setVisible(False)

    def set_buttons_text_icons(self):
        """
        Set button texts, icons, and style for open and save buttons.
        """
        self.btn_open.setText("打开")
        self.btn_open.setIcon(QIcon("./icon/open.png"))
        self.btn_open.setIconSize(QSize(36, 36))
        self.btn_open.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btn_open.clicked.connect(self.open_img)

        self.btn_save.setText("保存")
        self.btn_save.setIcon(QIcon("./icon/save.png"))
        self.btn_save.setIconSize(QSize(36, 36))
        self.btn_save.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btn_save.clicked.connect(self.save_img)

    def set_buttons_styles(self):
        """
        Set styles for all buttons and the control bar.
        """
        transparent_button_style = "background: rgba(0, 0, 0, 0); color: rgb(255, 255, 255);"
        gray_button_style = "background: rgb(80, 80, 80); color: rgb(255, 255, 255);"

        buttons = [self.btn_open, self.btn_save, self.btn_undo, self.btn_confirm, self.btn_cancel]
        for btn in buttons:
            btn.setStyleSheet(transparent_button_style)

        self.control_bar.setStyleSheet(gray_button_style)

    def set_layout_styles(self):
        """
        Set styles for central layout and title bar.
        """
        self.central_layout.setContentsMargins(0, 0, 0, 0)
        self.central_layout.setSpacing(0)

        self.title_bar.setMinimumSize(QSize(0, 55))
        self.title_bar.setMaximumSize(QSize(188888, 55))

        self.control_bar.setMinimumSize(QSize(0, 45))
        self.control_bar.setMaximumSize(QSize(120, 45))

        self.img_frame.setMinimumSize(QSize(100, 0))

        font = QtGui.QFont()
        font.setPointSize(8)
        self.setFont(font)
        self.btn_open.setFont(font)
        self.btn_save.setFont(font)

        self.central_widget.setStyleSheet("background: rgb(252, 255, 255);")
        self.title_bar.setStyleSheet("background: rgb(60, 60, 60);")

    def open_img(self):
        """
        Open an image file using a file dialog and display it.
        """
        img_name, img_type = QFileDialog.getOpenFileName(
            self, "打开图片", "", "*.jpg;*.png;*.jpeg"
        )
        if img_name == "" or img_name is None:
            self.show_warning_message_box("未选择图片")
            return

        img = cv2.imread(img_name)
        self.show_image(img)
        self.current_img = img
        self.last_img = self.current_img
        self.original_img = copy.deepcopy(self.current_img)
        self.original_img_path = img_name

    def show_image(self, img, is_grayscale=False):
        """
        Display an image in the ImageLabel widget.

        Args:
            img (numpy.ndarray): The image to display (in BGR or grayscale format).
            is_grayscale (bool, optional): Whether the image is grayscale. Defaults to False.
        """
        if len(img.shape) == 3:  # Color image
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # Convert BGR to RGB

        height, width, channels = img.shape
        bytes_per_line = channels * width

        if len(img.shape) == 2:  # Grayscale image
            format = QImage.Format_Grayscale8
            bytes_per_line *= 1  # Treat grayscale image as having one channel
        else:  # RGB image
            format = QImage.Format_RGB888

        qimage = QImage(img.data, width, height, bytes_per_line, format)
        pixmap = QPixmap.fromImage(qimage)
        if pixmap.width() > 600 or pixmap.height() > 600:
            pixmap = pixmap.scaled(600, 600, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self.img_display.setPixmap(pixmap)
        self.img_display.repaint()

    def crop_image(self, src_img, x_start, x_end, y_start, y_end):
        """
        Crop an image.

        Args:
            src_img (numpy.ndarray): The source image to crop.
            x_start (int): Starting x-coordinate of the crop region.
            x_end (int): Ending x-coordinate of the crop region.
            y_start (int): Starting y-coordinate of the crop region.
            y_end (int): Ending y-coordinate of the crop region.

        Returns:
            numpy.ndarray: The cropped image.
        """
        return src_img[y_start:y_end, x_start:x_end]

    def show_warning_message_box(self, msg):
        """
        Show a warning message box with the given message.

        Args:
            msg (str): The message to display.
        """
        QMessageBox.warning(self, "警告", msg, QMessageBox.Ok)

    def show_info_message_box(self, msg):
        """
        Show an information message box with the given message.

        Args:
            msg (str): The message to display.
        """
        QMessageBox.information(self, "提示", msg, QMessageBox.Ok)

    def save_img(self):
        """
        Save the current image to a file using a file dialog.
        """
        if self.current_img is None:
            self.show_warning_message_box("未选择图片")
            return

        ext_name = self.original_img_path[self.original_img_path.rindex(".") :]
        img_path, img_type = QFileDialog.getSaveFileName(
            self, "保存图片", self.original_img_path, f"*{ext_name}"
        )
        cv2.imwrite(img_path, self.current_img)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

标签:pixmap,bar,img,start,self,PyQT,OpenCV,btn,图片
From: https://blog.csdn.net/summerriver1/article/details/137397073

相关文章

  • MC14516BDR2G倒数计数器芯片中文资料PDF数据手册参数引脚图图片特性概述
    产品概述:MC14516B同步正数/倒数二进制计数器在一个单片结构中使用MOSP沟道和N沟道增强模式器件构造。此计数器可通过对预设输入(P0、P1、P2、P3)应用所需的二进制值,然后将预设启用(PE)置于高电平,来进行预设。计数方向通过向UP/DOWN输入应用高电平(用于正数)或低电平(用于......
  • FFmpeg图片与视频相互转换命令
    FFmpeg图片与视频相互转换命令命令简介该命令可以实现对图片和视频之间的相互转换,即:图片转视频和视频转图片。视频转图片命令的格式ffmpeg-i[输入文件][滤镜参数(可选)][输出文件]输入文件指定你要转哪个视频文件。滤镜参数详见FFmpeg滤镜命令简介,并且可以添加编解码......
  • Taro微信小程序--压缩图片组件
    图片压缩组件ImageCompress组件用于在Taro微信小程序中对图片进行压缩,以提高性能和优化加载。该组件利用了Taro框架来实现图片压缩功能。1,用法在页面导入ImageCompress组件。importImageCompressfrom'path/to/ImageCompress';传入指定参数<ImageCompressi......
  • [工具] png图片打包plist工具,手把手教你使用pngPackerGUI_V2.0
    png图片打包plist工具,手把手教你使用pngPackerGUI_V2.0此软件是在pngpacker_V1.1软件基础之后,开发的界面化操作软件,方便不太懂命令行的小白快捷上手使用。1.下载并解压缩软件,得到如下目录,双击打开pngPackerGUI.exe 2.打开pngPackerGUI之后,默认的界面如下: 3.选择目录:选......
  • [工具] 批量BMP图片转为PNG透明图片,去掉BMP黑色背景,去黑底,压缩导出png图片V1.1
    批量BMP图片转为PNG透明图片,去掉BMP黑色背景,压缩导出png图片V1.1前段时间上传了一款bmp转png并去黑底的demo软件,非常受欢迎,  上一版本地址:批量BMP图片转为PNG透明图片,去掉BMP黑色背景应广大爱好者的要求,完善了一下软件的功能,增加了导出png图片压缩功能,界面如下:  压......
  • opencv-python库 cv2图像二值化详解
    文章目录图像二值化原理cv2.threshold()Qtsu二值化cv2.adaptiveThreshold图像二值化原理图像二值化原理是通过设定一个阈值,将图像中的像素点的灰度值与阈值进行比较,大于阈值的像素点设置为白色,小于阈值的像素点设置为黑色1。图像二值化是将彩色或灰度图像转换为只包......
  • 在线生成占位图片工具:简便快捷的设计利器
    在网页开发或设计过程中,经常会遇到需要临时使用占位图片的情况。占位图片是指在设计阶段或者内容填充时使用的临时图片,用于模拟最终效果。这些占位图片通常用于展示页面布局、图片占位大小、颜色搭配等,以便设计师和开发人员更好地调整布局和设计。为了解决这个常见问题,出现了......
  • 一键下载,淘宝、天猫多张商品图片轻松搞定!
    在当今这个数字化飞速发展的时代,高效方法对于提升工作效率具有不可估量的价值。特别是对于电商从业者而言,及时获取高质量的商品图片对于店铺的运营和营销至关重要。电商平台里的商品许许多多,但手动下载多张商品图片却是一项耗时且费力的工作。这时高效方法的价值就显现出来了,小......
  • 韩顺平老师java坦克大战一些需要用到的东西(音乐文件、爆炸图片、音乐代码)(免费的!!!)
    首先非常感谢韩顺平老师的这个项目,虽然断断续续的完成了,但是收获很大,很有帮助。我的这些资源也是从别人那个弄来的,但是这个是整合版的。//音乐播放代码importjavax.sound.sampled.*;importjava.io.*;publicclassPlayAudioextendsThread{privateStringfi......
  • java压缩图片
    java压缩图片 importjavax.imageio.ImageIO;importjava.awt.image.BufferedImage;importjava.awt.Graphics2D;importjava.awt.Image;importjava.io.File;importjava.io.IOException;publicclassImageResizer{publicstaticvoidresizeImage(Stringi......