PyQT5在图像中选择ROI区域进行处理

1,331 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情

许多图像处理的交互界面允许用户自己通过鼠标选择一块特定的区域,并通过指定算法对该区域进行处理,一般用户选择的这块区域关注度较高,也被称为感兴趣区域(ROI),比如瑕疵检测时,用户可能更加关注最大瑕疵的长度、宽度或者瑕疵的面积等等,这关乎了瑕疵计算的等级问题,所以往往需要用户自己框定区域然后使用算法对该区域进行处理。

下面我通过一个简单的示例说明一下ROI区域的使用方法以及简单的算法实现。 界面文件下如下

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'ROIUI.ui'
#
# Created by: PyQt5 UI code generator 5.15.6
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(756, 664)
        self.horizontalLayoutWidget = QtWidgets.QWidget(Form)
        self.horizontalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 751, 41))
        self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)
        self.pushButton_2 = QtWidgets.QPushButton(self.horizontalLayoutWidget)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout.addWidget(self.pushButton_2)
        self.horizontalLayoutWidget_2 = QtWidgets.QWidget(Form)
        self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(0, 40, 750, 620))
        self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2)
        self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.label = QtWidgets.QLabel(self.horizontalLayoutWidget_2)
        self.label.setText("")
        self.label.setObjectName("label")
        self.horizontalLayout_2.addWidget(self.label)

        self.retranslateUi(Form)
        self.pushButton.clicked.connect(Form.OpenImg) # type: ignore
        self.pushButton_2.clicked.connect(Form.ProcessROI) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.pushButton.setText(_translate("Form", "打开图片"))
        self.pushButton_2.setText(_translate("Form", "处理ROI区域"))

image.png

大概长上面这个样子,先说明一下,界面文件在虽然有一个label控件,名字就叫label但是实际上并没有用,而是在下面的主文件中重写了QLabel,在重写过程中添加了鼠标事件,用于在选择ROI区域时可以用矩形框随即标记处选择的区域。

主文件如下

from PyQt5.QtWidgets import QWidget, QApplication, QLabel, QFileDialog, QMessageBox
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen
from ROIUI import Ui_Form
import cv2
import sys
import calGrade
import calROIGrade


# 重写QLabel,加上鼠标绘图事件
class MyLabel(QLabel):
    x0 = 0
    y0 = 0
    x1 = 0
    y1 = 0
    flag = False

    # 鼠标点击事件
    def mousePressEvent(self, event):
        self.flag = True
        self.x0 = event.x()
        self.y0 = event.y()

    # 鼠标释放事件
    def mouseReleaseEvent(self, event):
        self.flag = False

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

    # 绘制事件
    def paintEvent(self, event):
        super().paintEvent(event)
        rect = QRect(self.x0, self.y0, abs(self.x1-self.x0), abs(self.y1-self.y0))
        # A = self.x0
        painter = QPainter(self)
        painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
        painter.drawRect(rect)


class MainWindow(QWidget, Ui_Form):
    file = None
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent=parent)
        self.setupUi(self)
        self.lb = MyLabel(self)
        self.lb.setGeometry(QRect(4, 40, 750, 620))
        self.lb.setCursor(Qt.CrossCursor)
        self.file_path = None


    def OpenImg(self):
        try:
            self.file_path = QFileDialog.getOpenFileName(self, "select file", "./", "(Image File(*.jpg *.png *.bmp *.tiff))")
        except Exception as e:
            print(e)
        if self.file_path is not None and self.file_path[0] != '' and self.file_path[1] != '':
            img1 = cv2.imread(self.file_path[0])
            img_dis = QImage(img1, img1.shape[1], img1.shape[0], QImage.Format_BGR888)
            # 加载图片,并设定图片大小
            img = QPixmap(img_dis).scaled(int(img1.shape[1]), int(img1.shape[0]))
            width = img.width()  ##获取图片宽度
            height = img.height()  ##获取图片高度
            if width / self.lb.width() >= height / self.lb.height():  ##比较图片宽度与label宽度之比和图片高度与label高度之比
                ratio = width / self.lb.width()
            else:
                ratio = height / self.lb.height()
            new_width = int(width / ratio)  ##定义新图片的宽和高
            new_height = int(height / ratio)
            new_img = img.scaled(new_width, new_height)  ##调整图片尺寸
            # img_dis = QPixmap(img_dis).scaled(int(img.shape[1]), int(img.shape[0]))
            self.lb.setPixmap(new_img)
        else:
            QMessageBox.warning(self, "文件为空警告!", "你还未选择任何文件!")

    def detect(self,start_x, start_y, stop_x, stop_y):
        image = cv2.imread(self.file_path[0])
        img = image
        img1 = image
        if abs(stop_x - start_x) != 0 and abs(stop_y - start_y) != 0:
            img = img[start_y:stop_y, start_x:stop_x]

        img = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)


        if abs(stop_x - start_x) != 0 and abs(stop_y - start_y) != 0:
            img1[start_y:stop_y, start_x:stop_x] = img

        return img1

    def ROI(self):
        self.start_x = self.lb.x0
        self.start_y = self.lb.y0 - 5
        if self.start_x < 0 or self.start_y < 0 or self.start_y > 620 or self.start_x > 750:
            self.start_x = 0
            self.start_y = 0
        self.stop_x = self.lb.x1
        self.stop_y = self.lb.y1 - 5
        if self.stop_x < 0 or self.stop_y < 0 or self.stop_x > 750 or self.stop_y > 620:
            self.stop_x = 0
            self.stop_y = 0
        imageInfo = cv2.imread(self.file_path[0])

        st_x = imageInfo.shape[1] / self.lb.width()
        st_y = imageInfo.shape[0] / self.lb.height()
        file = self.file_path
        # print(file[0][0], file[0][1])
        img = self.detect(int(self.start_x*st_x), int(self.start_y*st_y), int(self.stop_x*st_x), int(self.stop_y*st_y))
        # cv2.imshow("test", img)
        # cv2.waitKey(0)
        img_dis = QImage(img, img.shape[1], img.shape[0], QImage.Format_BGR888)
        # 加载图片,并设定图片大小
        img_dis = QPixmap(img_dis).scaled(int(img.shape[1]), int(img.shape[0]))
        # 显示图片
        return img_dis

    def ProcessROI(self):
        img = self.ROI()
        width = img.width()  ##获取图片宽度
        height = img.height()  ##获取图片高度
        if width / self.lb.width() >= height / self.lb.height():  ##比较图片宽度与label宽度之比和图片高度与label高度之比
            ratio = width / self.lb.width()
        else:
            ratio = height / self.lb.height()
        new_width = int(width / ratio)  ##定义新图片的宽和高
        new_height = int(height / ratio)
        new_img = img.scaled(new_width, new_height)  ##调整图片尺寸
        # img_dis = QPixmap(img_dis).scaled(int(img.shape[1]), int(img.shape[0]))
        self.lb.setPixmap(new_img)


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

下面是处理时的效果,由于本例只说明对于ROI选择以及对选择区域进行简单的处理,所以功能很简单,就是对用户鼠标选择的区域,将该区域的图像颜色由BGR转换为YCrCb。

image.png 只计算了大概的矩形框区域进行处理,所以处理区域比实际选择的区域要小,所以看起来被处理的区域显得短了一截。

有需要的只需要将代码复制后在detect里面加入自己的处理函数即可。