截图时候,要考虑图片所在坐标系的问题,以免出现了截图之后,放大图片非常模糊的情况。因此本文引用了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请自行处理环境问题。