opencv+pyqt选择区域切割图片

截图时候,要考虑图片所在坐标系的问题,以免出现了截图之后,放大图片非常模糊的情况。因此本文引用了four_point_transform的方法对待切割部分的四角标定,之后截出正常的图片。

该案例包括的三部分:

1)

class ImageViewer(QLabel):整合了一些高级功能,包括图像的加载、显示、动态缩放和矩形选择。这个类是基于 PyQt5 的 QLabel扩展的,用于处理图像显示和用户交互。

2)

class FloatingDialog(QDialog):创建的 FloatingDialog 类是一个定制的对话框,继承自 PyQt5 中的 QDialog,专为特定用例而设计,用户需要在执行操作之前确认一项操作——通常是从下拉菜单中选择一个选项。

3)

class MainWindow(QMainWindow):是一个基于 PyQt5 的主窗口类 MainWindow,其中包含了打开图片、显示浮动对话框、处理透视变换和取消操作的功能。

代码集合:

import sys
import cv2
import numpy as np

from imutils.perspective import four_point_transform
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QVBoxLayout,
                             QPushButton, QFileDialog, QWidget, QMessageBox,
                             QHBoxLayout, QDialog, QComboBox)
from PyQt5.QtGui import QPixmap, QImage, QPainter, QPen
from PyQt5.QtCore import Qt, QRect, QPointF

class ImageViewer(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMouseTracking(True)
        self.points = []  # 用于存储矩形的顶点
        self.start_point = None  # 矩形的起点
        self.end_point = None  # 矩形的终点
        self.drawing = False  # 是否正在绘制矩形
        self.rectangles = []  # 用于存储矩形的列表
        self.cv_image = None  # 用于存储加载的图像
        self.scale_factor = 1.0  # 缩放因子

# 导入图片
    def set_image(self, image_path):
        self.image_path = image_path
        self.cv_image = cv2.imread(image_path)
        if self.cv_image is None:
            QMessageBox.critical(self, "错误", "无法打开图像文件,请检查文件路径和文件完整性")
            return
        self.update_image()
        self.reset_rectangles()  # 重置矩形

    def update_image(self):
        if self.cv_image is not None:
            # 获取图像的高度、宽度和通道数
            height, width, channel = self.cv_image.shape
            bytes_per_line = 3 * width
            # 将cv_image转换为QImage格式(cv 的图片展现形式与 界面的图片显示
            # )
            q_image = QImage(self.cv_image.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped()
            pixmap = QPixmap.fromImage(q_image)
            # 计算缩放因子以适应窗口大小
            self.scale_factor = min(self.width() / pixmap.width(), self.height() / pixmap.height())
            # 缩放图像
            scaled_pixmap = pixmap.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
            self.setPixmap(scaled_pixmap)
# 图片伸缩响应
    def resizeEvent(self, event):
        self.update_image()  # 在窗口调整大小时更新图像显示

    def update_cv_image(self, new_cv_image):
        self.cv_image = new_cv_image
        self.update_image()  # 更新图像显示
# 图像绘制事件
    def paintEvent(self, event):
        super().paintEvent(event)
        painter = QPainter(self)
        pen = QPen(Qt.green, 2, Qt.SolidLine)  # 设置画笔颜色和粗细
        painter.setPen(pen) # 画笔设置
        for rect in self.rectangles:
            painter.drawRect(rect)  # 绘制矩形
        if self.drawing and self.start_point and self.end_point:
            rect = QRect(self.start_point, self.end_point)
            painter.drawRect(rect)  # 绘制当前正在绘制的矩形

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.drawing = True
            self.start_point = event.pos()  # 获取起点位置
            self.points = [self.map_to_original(self.start_point)]
            print(f"Start Point: {self.start_point}")

            self.reset_rectangles()  # 重置矩形

    def mouseMoveEvent(self, event):
        if self.drawing:
            self.end_point = event.pos()  # 获取终点位置
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.drawing:
            self.drawing = False
            self.end_point = event.pos()
            self.points.append(self.map_to_original(self.end_point))
            rect = QRect(self.start_point, self.end_point)
            self.rectangles.append(rect)  # 添加矩形到列表中
            self.update()

            self.points = [
                self.map_to_original(self.start_point),
                self.map_to_original(QPointF(self.end_point.x(), self.start_point.y())),
                self.map_to_original(self.end_point),
                self.map_to_original(QPointF(self.start_point.x(), self.end_point.y()))
            ]

            print(f"End Point: {self.end_point}")
            print(f"Points: {self.points}")

            self.start_point = None
            self.end_point = None

    def map_to_original(self, point):
        # 将点坐标映射到原始图像坐标系
        return QPointF(point.x() / self.scale_factor, point.y() / self.scale_factor)

    def reset_rectangles(self):
        self.rectangles = []  # 重置矩形列表
        self.points = []  # 重置点列表
        self.update()

class FloatingDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("确认保存")
        self.combo_box = QComboBox(self)
        self.combo_box.addItem("请选择对应题号")
        self.combo_box.addItems(["题号1", "题号2", "题号3"])  # 示例题号,可以根据需要进行修改
        self.confirm_button = QPushButton("确定", self)
        self.cancel_button = QPushButton("取消", self)

        layout = QVBoxLayout()
        layout.addWidget(self.combo_box)
        button_layout = QHBoxLayout()
        button_layout.addWidget(self.confirm_button)
        button_layout.addWidget(self.cancel_button)
        layout.addLayout(button_layout)

        self.setLayout(layout)

        self.confirm_button.clicked.connect(self.accept)
        self.cancel_button.clicked.connect(self.reject)


# 主窗口
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.img_counter = 1  # 用于图像保存的计数器
        self.image_path = None  # 用于存储图像路径

    def init_ui(self):
        self.image_viewer = ImageViewer(self)
        self.open_button = QPushButton('打开图片')
        self.confirm_button = QPushButton('确认截图')

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.open_button)
        button_layout.addWidget(self.confirm_button)

        layout = QVBoxLayout()
        layout.addWidget(self.image_viewer)
        layout.addLayout(button_layout)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        self.open_button.clicked.connect(self.open_image)
        self.confirm_button.clicked.connect(self.show_floating_dialog)

        self.floating_dialog = FloatingDialog(self)
        self.floating_dialog.finished.connect(self.on_floating_dialog_closed)

    def open_image(self):
        # 打开文件对话框以选择图像文件
        image_path, _ = QFileDialog.getOpenFileName(self, '打开图片', '', 'Image files (*.jpg *.jpeg *.png)')
        if image_path:
            self.image_path = image_path
            self.image_viewer.set_image(image_path)
            print(f"Image opened: {self.image_path}")

    def show_floating_dialog(self):
        # 在显示浮动对话框前检查是否绘制了完整的矩形框
        if len(self.image_viewer.points) != 4:
            QMessageBox.warning(self, "警告", "请绘制一个完整的矩形来进行透视变换")
        else:
            self.floating_dialog.combo_box.setCurrentIndex(0)  # 重置下拉框
            self.floating_dialog.exec_()

    def on_floating_dialog_closed(self, result):
        if result == QDialog.Accepted:
            question_number = self.floating_dialog.combo_box.currentText()
            if question_number == "请选择对应题号":
                QMessageBox.warning(self, "警告", "请选择一个题号")
                return
            print(f"保存题号: {question_number}")
        else:
            print("取消操作")

        self.perform_image_cut()

    def perform_image_cut(self):
        if len(self.image_viewer.points) == 4:
            pts = np.array([(point.x(), point.y()) for point in self.image_viewer.points], dtype="float32")
            print(f"Points for perspective transform: {pts}")

            if self.image_viewer.cv_image is None:
                print("cv_image is None")
                return

            # 检查输入图像
            if self.image_viewer.cv_image.size == 0:
                print("输入图像为空")
                return

            warped = four_point_transform(self.image_viewer.cv_image, pts)
            if warped is None or warped.size == 0:
                print("透视变换后的图像为空")
                return

            # 保存并显示透视变换后的图像
            transformed_img_path = f'transformed_image_{self.img_counter}.png'
            cv2.imwrite(transformed_img_path, warped)
            print(f"保存透视变换后的图像: {transformed_img_path}")

            self.img_counter += 1

            self.image_viewer.reset_rectangles()

            self.image_viewer.set_image(self.image_path)

        else:
            QMessageBox.warning(self, "警告", "请绘制一个完整的矩形来进行透视变换")

    def cancel_capture(self):
        self.image_viewer.reset_rectangles()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

pip请自行处理环境问题。

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值