Bootstrap

QT5.12+VS2019 自定义QLabel ROI Mask绘制生成工具

目录

一、工具功能

二、实现效果

三、关键代码

四、模块集成

一、工具功能

1、支持矩形、圆(椭圆)、多边形基础图形绘制工具;

2、支持各图形工具正选(填充)、反选(抠除)属性绘制;

3、支持绘制对象选中、移动操作(双击选中后右键拖拽移动);

4、支持绘制对象选中删除操作(双击选中、Del键删除);

5、支持绘制对象正选、反选极性切换(Space键);

6、支持对已选中的绘制对象进行键盘方向键微调操作(← ↑ ↓ →键);

7、支持ESC键退出选中或退出多边形绘制状态(ESC键);

8、显示绘制标尺(方便绘制时边界对齐,特别是椭圆绘制);

9、生成与输入图像同分辨率的二值Mask图像;

二、实现效果

三、关键代码

从QLabel派生自定义类:RoiTools

class RoiTools : public QLabel {
    Q_OBJECT
public:
    explicit RoiTools(QWidget* parent = nullptr);
    ...
protected:
    ...

private:
    ...
signals:
    ...
private slots:
    ...
};

 重写mousePressEvent事件函数,捕获鼠标左右键,左键绘制捕获,右键移动捕获。

void RoiTools::mousePressEvent(QMouseEvent* event) {
	if (pix.isNull())
	{
		return;
	}
	if (!this->allow_drawing) return;

    this->allow_moving = false;

	if (event->button() == Qt::LeftButton) {

		start = event->pos();

		end = start;

		drawing = true;

		if (shapeType == ShapeType::Polygon) {
            polygon_drawing = true;
			//polygon << start;
		}
		update();
	}
	else if (event->button() == Qt::RightButton)
	{
        right_btn_press = event->pos();

        this->allow_moving = true;

        if (rect_sel_index != -1)
        {
            selected_rect = roi_rects.at(rect_sel_index).rect;
        }

        if (ellipse_sel_index != -1)
        {
            selected_ellipse = roi_ellipses.at(ellipse_sel_index).rect;
        }

        if (polygon_sel_index != -1)
        {
            selected_polygon = roi_polygons.at(polygon_sel_index).polygon;
        }
	}    
}

重写mouseMoveEvent事件函数。绘制多边形时最后一个点实时跟随鼠标当前位置,绘制对象选中状态检测鼠标右键移动。

void RoiTools::mouseMoveEvent(QMouseEvent* event) {

	if (!this->allow_drawing) return;

	if (drawing && !moving) {
		end = event->pos();
	}
    mouse_point = event->pos();

	if (shapeType == ShapeType::Polygon && polygon_drawing) 
    {
		int p_cnt = polygon.count();

		//绘制多边形时跟随当前鼠标,当前鼠标坐标替换按键按下时添加的占位坐标
		if (p_cnt > 1)
		{
			polygon[p_cnt - 1] = event->pos();   
		}
	}

	if (rect_sel_index != -1 && allow_moving)
	{
		qreal dx = (static_cast<qreal>(right_btn_press.x()) - event->pos().x()) / scaledSize.width();
		qreal dy = (static_cast<qreal>(right_btn_press.y()) - event->pos().y()) / scaledSize.height();

		roi_rects[rect_sel_index].rect.setLeft(selected_rect.left() - dx);
		roi_rects[rect_sel_index].rect.setRight(selected_rect.right() - dx);

		roi_rects[rect_sel_index].rect.setTop(selected_rect.top() - dy);
		roi_rects[rect_sel_index].rect.setBottom(selected_rect.bottom() - dy);

	}

    if (ellipse_sel_index != -1 && allow_moving)
    {
        qreal dx = (static_cast<qreal>(right_btn_press.x()) - event->pos().x()) / scaledSize.width();
        qreal dy = (static_cast<qreal>(right_btn_press.y()) - event->pos().y()) / scaledSize.height();

        roi_ellipses[ellipse_sel_index].rect.setLeft(selected_ellipse.left() - dx);
        roi_ellipses[ellipse_sel_index].rect.setRight(selected_ellipse.right() - dx);

        roi_ellipses[ellipse_sel_index].rect.setTop(selected_ellipse.top() - dy);
        roi_ellipses[ellipse_sel_index].rect.setBottom(selected_ellipse.bottom() - dy);

    }

	if (polygon_sel_index != -1 && allow_moving)
	{
		qreal dx = (static_cast<qreal>(right_btn_press.x()) - event->pos().x()) / scaledSize.width();
		qreal dy = (static_cast<qreal>(right_btn_press.y()) - event->pos().y()) / scaledSize.height();

		QPolygonF t_polygon;

		for (int i = 0; i < selected_polygon.count(); i++)
		{
			QPointF p;
			p.setX(selected_polygon.at(i).x() - dx);
			p.setY(selected_polygon.at(i).y() - dy);
			t_polygon.append(p);
		}

		roi_polygons[polygon_sel_index].polygon = t_polygon;
    }

	this->update();
}

重写mouseReleaseEvent事件函数。左键抬起时结束矩形、圆绘制,右键抬起时move动作结束。

void RoiTools::mouseReleaseEvent(QMouseEvent* event) {

    if (!this->allow_drawing) return;
        
    if ((shapeType == ShapeType::Rectangle || shapeType == ShapeType::Ellipse) &&
        (abs(start.x() - event->pos().x()) < DEAD_ZONE && abs(start.y() - event->pos().y()) < DEAD_ZONE))
    {
        drawing = false;
        allow_moving = false;
        this->start = QPoint(-1,-1);
        this->end = QPoint(-1, -1);
        return;
    }
    if (event->button() == Qt::LeftButton) 
    {
        if (shapeType == ShapeType::Polygon && drawing) {
            //删除最后一个move时的坐标
            if (polygon.count() > 1)
            {
                polygon.removeLast();
            }
            polygon << event->pos();
            //添加两次,第二次给move时鼠标跟随占位
            polygon << event->pos();
        }

        drawing = false;

        if (shapeType == ShapeType::Rectangle)
        {
            Roi_Rect rect;
            rect.rect.setLeft(static_cast<float>(start.x() - display_offset.x()) / scaledSize.width());
            rect.rect.setTop(static_cast<float>(start.y() - display_offset.y()) / scaledSize.height());

            rect.rect.setRight(static_cast<float>(end.x() - display_offset.x()) / scaledSize.width());
            rect.rect.setBottom(static_cast<float>(end.y() - display_offset.y()) / scaledSize.height());

            rect.polarity = this->polarity;

            roi_rects.append(rect);

            //rect_stack.push(roi_rects.last().rect);
        }
        else if (shapeType == ShapeType::Ellipse)
        {
            Roi_Ellipse ellp;
            ellp.rect.setLeft(static_cast<float>(start.x() - display_offset.x()) / scaledSize.width());
            ellp.rect.setTop(static_cast<float>(start.y() - display_offset.y()) / scaledSize.height());

            ellp.rect.setRight(static_cast<float>(end.x() - display_offset.x()) / scaledSize.width());
            ellp.rect.setBottom(static_cast<float>(end.y() - display_offset.y()) / scaledSize.height());

            ellp.polarity = this->polarity;

            roi_ellipses.append(ellp);
        }
        else if (shapeType == ShapeType::Polygon)
        {

        }
        //清除动态显示
        this->start = QPointF(-1, -1);
        this->end = QPointF(-1, -1);

        this->allow_moving = false;
    }

    else if (event->button() == Qt::RightButton)
    {
        allow_moving = false;
    }

    this->genMask();
        
    update();
}

重写mouseDoubleClickEvent事件函数。多边形绘制时鼠标双击结束绘制,非多边形绘制模式,鼠标双击检测鼠标位置是否包含绘制对象,若是,则进行选中。

选中捕获时,优先捕获反选对象,再捕获正选对象。

void RoiTools::mouseDoubleClickEvent(QMouseEvent* event)
{
    if (!this->allow_drawing) return;

    if (event->button() == Qt::LeftButton)
    {
        if (shapeType == ShapeType::Rectangle || shapeType == ShapeType::Ellipse)
        {
            start = event->pos();
            end = event->pos();
            this->update();
        }
        if (shapeType == ShapeType::Polygon)
        {//双击事件必定产生前两点,寻址无越界风险
            //polygon << event->pos();

            int dx = abs(polygon.at(0).x() - polygon.at(1).x());
            int dy = abs(polygon.at(0).y() - polygon.at(1).y());

            //多边形前两点相距过短 舍弃
            if (dx < DEAD_ZONE && dy < DEAD_ZONE)
            {
                polygon.clear();
            }
            else
            {
                polygon.removeLast();
            }

            if (polygon.count() == 2)
            {
                polygon.clear();
            }
            //多边形有效 结束绘制
            if (polygon.count() > 0)
            {
                Roi_Polygon po;

                for (int i = 0; i < polygon.count(); i++)
                {
                    QPointF t_p;

                    t_p.setX(static_cast<float>(polygon.at(i).x() - display_offset.x()) / scaledSize.width());
                    t_p.setY(static_cast<float>(polygon.at(i).y() - display_offset.y()) / scaledSize.height());

                    po.polygon.append(t_p);

                    po.polarity = this->polarity;
                }

                roi_polygons.append(po);

                polygon_drawing = false;

                polygon.clear();

                //及时返回 打断绘制结束后立即触发选中检测
                return;
            }
        }
        //清除选中状态
        //若未被任意roi捕获,默认取消选择
        this->clearSelectState();

        //优先捕获反极性Rect
        for (int i = 0; i < roi_rects.count(); i++)
        {
            if (roi_rects.at(i).polarity == Polarity::Positive) continue;

            QPointF ss, ee;

            ss.setX(roi_rects.at(i).rect.topLeft().x() * scaledSize.width() + display_offset.x());
            ss.setY(roi_rects.at(i).rect.topLeft().y() * scaledSize.height() + display_offset.y());

            ee.setX(roi_rects.at(i).rect.bottomRight().x() * scaledSize.width() + display_offset.x());
            ee.setY(roi_rects.at(i).rect.bottomRight().y() * scaledSize.height() + display_offset.y());

            QRectF re = QRectF(ss, ee);

            if (re.contains(event->pos()))
            {
                rect_sel_index = i;
                selected_rect = roi_rects.at(i).rect;
                roi_rects[i].is_selected = true;
                this->update();
                return;
            }
            else
            {
                qDebug() << "No element!";
            }
        }
        //优先捕获反极性Ellipse
        for (int i = 0; i < roi_ellipses.count(); i++)
        {
            if (roi_ellipses.at(i).polarity == Polarity::Positive) continue;

            QPointF ss, ee;

            ss.setX(roi_ellipses.at(i).rect.topLeft().x() * scaledSize.width() + display_offset.x());
            ss.setY(roi_ellipses.at(i).rect.topLeft().y() * scaledSize.height() + display_offset.y());

            ee.setX(roi_ellipses.at(i).rect.bottomRight().x() * scaledSize.width() + display_offset.x());
            ee.setY(roi_ellipses.at(i).rect.bottomRight().y() * scaledSize.height() + display_offset.y());

            QRectF re = QRectF(ss, ee);

            if (isPointInsideEllipse(event->pos(), re))
            //if (re.contains(event->pos()))
            {
                ellipse_sel_index = i;
                selected_ellipse = roi_ellipses.at(i).rect;
                roi_ellipses[i].is_selected = true;
                this->update();
                return;
            }
            else
            {
                qDebug() << "No element!";
            }
        }

        //优先捕获反极性Polygon
        QPolygonF t_polygon;
        for (int i = 0; i < roi_polygons.count(); i++)
        {
            if (roi_polygons.at(i).polarity == Polarity::Positive) continue;

            for (int j = 0; j < roi_polygons.at(i).polygon.count(); j++)
            {
                QPointF ss;

                ss.setX(roi_polygons.at(i).polygon.at(j).x() * scaledSize.width() + display_offset.x());
                ss.setY(roi_polygons.at(i).polygon.at(j).y() * scaledSize.height() + display_offset.y());

                t_polygon.append(ss);
            }

            //判断鼠标位置是否在多边形区域内
            if (isPointInsidePolygon(event->pos(), t_polygon))
            {
                polygon_sel_index = i;
                selected_polygon = roi_polygons.at(i).polygon;
                roi_polygons[i].is_selected = true;
                this->update();
                return;
            }
            else
            {
                //qDebug() << "No element!" << event->pos();
            }
        }
        

        //捕获正极性Rect
        for (int i = 0; i < roi_rects.count(); i++)
        {
            if (roi_rects.at(i).polarity == Polarity::Negative) continue;

            QPointF ss, ee;

            ss.setX(roi_rects.at(i).rect.topLeft().x() * scaledSize.width() + display_offset.x());
            ss.setY(roi_rects.at(i).rect.topLeft().y() * scaledSize.height() + display_offset.y());

            ee.setX(roi_rects.at(i).rect.bottomRight().x() * scaledSize.width() + display_offset.x());
            ee.setY(roi_rects.at(i).rect.bottomRight().y() * scaledSize.height() + display_offset.y());

            QRectF re = QRectF(ss, ee);

            if (re.contains(event->pos()))
            {
                rect_sel_index = i;
                selected_rect = roi_rects.at(i).rect;
                roi_rects[i].is_selected = true;
                this->update();
                return;
            }
            else
            {
                //qDebug() << "No element!";
            }
        }

        //优先捕获正极性Ellipse
        for (int i = 0; i < roi_ellipses.count(); i++)
        {
            if (roi_ellipses.at(i).polarity == Polarity::Negative) continue;

            QPointF ss, ee;

            ss.setX(roi_ellipses.at(i).rect.topLeft().x() * scaledSize.width() + display_offset.x());
            ss.setY(roi_ellipses.at(i).rect.topLeft().y() * scaledSize.height() + display_offset.y());

            ee.setX(roi_ellipses.at(i).rect.bottomRight().x() * scaledSize.width() + display_offset.x());
            ee.setY(roi_ellipses.at(i).rect.bottomRight().y() * scaledSize.height() + display_offset.y());

            QRectF re = QRectF(ss, ee);


            if (isPointInsideEllipse(event->pos(), re))
            //if (re.contains(event->pos()))
            {
                ellipse_sel_index = i;
                selected_ellipse = roi_ellipses.at(i).rect;
                roi_ellipses[i].is_selected = true;
                this->update();
                return;
            }
            else
            {
                //qDebug() << "No element!";
            }
        }

        //优先捕获正极性Polygon
        QPolygonF t1_polygon;
        for (int i = 0; i < roi_polygons.count(); i++)
        {
            if (roi_polygons.at(i).polarity == Polarity::Negative) continue;

            for (int j = 0; j < roi_polygons.at(i).polygon.count(); j++)
            {
                QPointF ss;

                ss.setX(roi_polygons.at(i).polygon.at(j).x() * scaledSize.width() + display_offset.x());
                ss.setY(roi_polygons.at(i).polygon.at(j).y() * scaledSize.height() + display_offset.y());

                t1_polygon.append(ss);
            }

            if (isPointInsidePolygon(event->pos(), t1_polygon))
            {
                polygon_sel_index = i;
                selected_polygon = roi_polygons.at(i).polygon;
                roi_polygons[i].is_selected = true;
                this->update();
                return;
            }
            else
            {
                qDebug() << "No element!" << event->pos();
            }
        }
    }
    this->update();
}

重写paintEvent函数。实现绘制图形静态动态刷新。

void RoiTools::paintEvent(QPaintEvent*) 
{
    if (pix.isNull())
    {
        return;
    }

    QPainter painter(this);

    painter.setRenderHint(QPainter::Antialiasing);


    if (state_visible)
    {
        QFont font(QString::fromLocal8Bit("微软雅黑"), 12); // 使用微软雅黑字体,字号为12
        painter.setFont(font);
        painter.drawText(QPoint(8, 16), state_str);
    }

    // 获取label大小
    QSize size = this->size();

    // 计算label和image的横纵比
    qreal labelAspectRatio = qreal(size.width()) / qreal(size.height());
    qreal imageAspectRatio = qreal(pix.width()) / qreal(pix.height());

    // 如果标签的纵横比大于图像的纵横比,则以标签的高度为准缩放图像
    if (labelAspectRatio > imageAspectRatio) {
        scaledSize.setHeight(size.height());
        scaledSize.setWidth(size.height() * imageAspectRatio);
    }
    // 否则以标签的宽度为准缩放图像
    else {
        scaledSize.setWidth(size.width());
        scaledSize.setHeight(size.width() / imageAspectRatio);
    }

    // 缩放图像
    QPixmap scaledPixmap = pix.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);

    // 图像居中显示
    qreal offset_x = (size.width() - scaledPixmap.width()) / 2.0f;
    qreal offset_y = (size.height() - scaledPixmap.height()) / 2.0f;

	painter.drawPixmap(offset_x, offset_y, scaledPixmap);

    if (this->shapeType == ShapeType::Rectangle)
    {
        if(this->polarity == Polarity::Positive)
        {
            painter.setPen(QColor(0,255,0,0));
            painter.setBrush(QColor(Qt::green));
            painter.drawRect(QRect(offset_x + TIP_SIZE, offset_y + TIP_SIZE, TIP_SIZE, TIP_SIZE));
        }
        else if (this->polarity == Polarity::Negative)
        {
            painter.setPen(QColor(255, 0, 0, 0));
            painter.setBrush(QColor(Qt::red));
            painter.drawRect(QRect(offset_x + TIP_SIZE, offset_y + TIP_SIZE, TIP_SIZE, TIP_SIZE));
        }
    }
    else if (this->shapeType == ShapeType::Ellipse)
    {
        if (this->polarity == Polarity::Positive)
        {
            painter.setPen(QColor(0, 255, 0, 0));
            painter.setBrush(QColor(Qt::green));
            painter.drawEllipse(QRect(offset_x + TIP_SIZE, offset_y + TIP_SIZE, TIP_SIZE, TIP_SIZE));
        }
        else if (this->polarity == Polarity::Negative)
        {
            painter.setPen(QColor(255, 0, 0, 0));
            painter.setBrush(QColor(Qt::red));
            painter.drawEllipse(QRect(offset_x + TIP_SIZE, offset_y + TIP_SIZE, TIP_SIZE, TIP_SIZE));
        }
    }
    else if (this->shapeType == ShapeType::Polygon)
    {
        if (this->polarity == Polarity::Positive)
        {
            painter.setPen(QColor(0, 255, 0, 0));
            painter.setBrush(QColor(Qt::green));
            QPolygonF p;
            p << QPointF(offset_x + TIP_SIZE, offset_y + TIP_SIZE) << QPointF(offset_x + TIP_SIZE, offset_y + TIP_SIZE * 2.0f) << QPointF(offset_x + TIP_SIZE * 2.0f, offset_y + TIP_SIZE + TIP_SIZE / 2.0f);
            painter.drawPolygon(p);
        }
        else if (this->polarity == Polarity::Negative)
        {
            painter.setPen(QColor(255, 0, 0, 0));
            painter.setBrush(QColor(Qt::red));
            QPolygonF p;
            p << QPointF(offset_x + TIP_SIZE, offset_y + TIP_SIZE) << QPointF(offset_x + TIP_SIZE, offset_y + TIP_SIZE * 2.0f) << QPointF(offset_x + TIP_SIZE * 2.0f, offset_y + TIP_SIZE + TIP_SIZE / 2.0f);
            painter.drawPolygon(p);
        }
    }

	display_offset = QPointF(offset_x, offset_y);

	if (this->polarity == Polarity::Positive)
	{
		painter.setPen(QPen(QBrush(QColor(Qt::green)), 2));
		painter.setBrush(QColor(0, 255, 0, 10));
	}
	else if (this->polarity == Polarity::Negative)
	{
		painter.setPen(QPen(QBrush(QColor(Qt::red)), 2));
		painter.setBrush(QColor(255, 0, 0, 10));
	}
	//动态显示
	if (shapeType == ShapeType::Rectangle && drawing)
	{
		//动态显示
		painter.drawRect(QRectF(start, end));
	}
	else if (shapeType == ShapeType::Ellipse)
	{
		//动态显示
		painter.drawEllipse(QRectF(start, end));
	}
	else if (shapeType == ShapeType::Polygon) 
    {
        //动态显示
		painter.drawPolygon(QPolygon(polygon));
	}

    //静态显示
    //rects
	for (int i = 0; i < roi_rects.count(); i++)
	{
		if (roi_rects.at(i).polarity == Polarity::Positive)
		{
            QPen pen(Qt::green);
            pen.setWidth(2);
            if (roi_rects.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
			painter.setPen(pen);
            painter.setBrush(QColor(0, 255, 0, 40));
		}
		else if (roi_rects.at(i).polarity == Polarity::Negative)
		{
            QPen pen(Qt::red);
            pen.setWidth(2);
            if (roi_rects.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
			painter.setPen(pen);
            painter.setBrush(QColor(255, 0, 0, 40));
		}
        else if(roi_rects.at(i).polarity == Polarity::None)
        {
            QPen pen(Qt::white);
            pen.setWidth(2);
            if (roi_rects.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
            painter.setPen(pen);
            painter.setBrush(QColor(255, 255, 255, 40));
        }

		QPointF ss, ee;
		ss.setX(roi_rects.at(i).rect.topLeft().x() * scaledSize.width() + display_offset.x());
		ss.setY(roi_rects.at(i).rect.topLeft().y() * scaledSize.height() + display_offset.y());

		ee.setX(roi_rects.at(i).rect.bottomRight().x() * scaledSize.width() + display_offset.x());
		ee.setY(roi_rects.at(i).rect.bottomRight().y() * scaledSize.height() + display_offset.y());

		painter.drawRect(QRectF(ss, ee));
	}

    //静态显示
    //ellps
	for (int i = 0; i < roi_ellipses.count(); i++)
	{
        if (roi_ellipses.at(i).polarity == Polarity::Positive)
        {
            QPen pen(Qt::green);
            pen.setWidth(2);
            if (roi_ellipses.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
            painter.setPen(pen);
            painter.setBrush(QColor(0, 255, 0, 40));
        }
        else if (roi_ellipses.at(i).polarity == Polarity::Negative)
        {
            QPen pen(Qt::red);
            pen.setWidth(2);
            if (roi_ellipses.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
            painter.setPen(pen);
            painter.setBrush(QColor(255, 0, 0, 40));
        }
        else if (roi_ellipses.at(i).polarity == Polarity::None)
        {
            QPen pen(Qt::white);
            pen.setWidth(2);
            if (roi_ellipses.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
            painter.setPen(pen);
            painter.setBrush(QColor(255, 255, 255, 40));
        }

		QPointF ss, ee;
		ss.setX(roi_ellipses.at(i).rect.topLeft().x() * scaledSize.width() + display_offset.x());
		ss.setY(roi_ellipses.at(i).rect.topLeft().y() * scaledSize.height() + display_offset.y());

		ee.setX(roi_ellipses.at(i).rect.bottomRight().x() * scaledSize.width() + display_offset.x());
		ee.setY(roi_ellipses.at(i).rect.bottomRight().y() * scaledSize.height() + display_offset.y());

		painter.drawEllipse(QRectF(ss, ee));
	}

    //静态显示
    //polygons
	for (int i = 0; i < roi_polygons.count(); i++)
	{

        if (roi_polygons.at(i).polarity == Polarity::Positive)
        {
            QPen pen(Qt::green);
            pen.setWidth(2);
            if (roi_polygons.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
            painter.setPen(pen);
            painter.setBrush(QColor(0, 255, 0, 40));
        }
        else if (roi_polygons.at(i).polarity == Polarity::Negative)
        {
            QPen pen(Qt::red);
            pen.setWidth(2);
            if (roi_polygons.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
            painter.setPen(pen);
            painter.setBrush(QColor(255, 0, 0, 40));
        }
        else if (roi_polygons.at(i).polarity == Polarity::None)
        {
            QPen pen(Qt::white);
            pen.setWidth(2);
            if (roi_polygons.at(i).is_selected)
            {
                pen.setStyle(Qt::DotLine);
            }
            else
            {
                pen.setStyle(Qt::SolidLine);
            }
            painter.setPen(pen);
            painter.setBrush(QColor(255, 255, 255, 40));
        }

		QPolygonF p;

		for (int j = 0; j < roi_polygons.at(i).polygon.count(); j++)
		{
			QPointF pnt;
			pnt.setX(roi_polygons.at(i).polygon.at(j).x() * scaledSize.width() + display_offset.x());
			pnt.setY(roi_polygons.at(i).polygon.at(j).y() * scaledSize.height() + display_offset.y());

			p.append(pnt);
		}
		painter.drawPolygon(p);
	}

    if (need_draw_ruler)
    {
        QLine hor_line(QPoint(offset_x, mouse_point.y()), QPoint(scaledSize.width() + offset_x, mouse_point.y()));

        QLine ver_line(QPoint(mouse_point.x(), offset_y), QPoint(mouse_point.x(), scaledSize.height() + offset_y));
        QPen pen = QPen(Qt::green);
        pen.setStyle(Qt::DashLine);
        painter.setPen(pen);
        painter.drawLine(hor_line);
        painter.drawLine(ver_line);
    }

}

辅助函数1:检测鼠标点是否在多边形区域内(射线检测算法)

bool RoiTools::isPointInsidePolygon(const QPointF& point, const QPolygonF& polygon) {
    int intersectCount = 0;
    QPointF p1, p2;

    int numPoints = polygon.size();
    p1 = polygon.at(0);

    for (int i = 1; i <= numPoints; i++) {
        p2 = polygon.at(i % numPoints);

        if (point.y() > std::min(p1.y(), p2.y())) {
            if (point.y() <= std::max(p1.y(), p2.y())) {
                if (point.x() <= std::max(p1.x(), p2.x())) {
                    if (p1.y() != p2.y()) {
                        double xIntersection = (point.y() - p1.y()) * (p2.x() - p1.x()) / (p2.y() - p1.y()) + p1.x();
                        if (p1.x() == p2.x() || point.x() <= xIntersection)
                            intersectCount++;
                    }
                }
            }
        }
        p1 = p2;
    }

    return intersectCount % 2 == 1;
}

辅助函数2:检测鼠标点是否在椭圆区域内

bool RoiTools::isPointInsideEllipse(const QPointF& point, const QRectF& ellipseRect) {
    
    qreal x0 = ellipseRect.center().x(); // 椭圆中心的 x 坐标
    qreal y0 = ellipseRect.center().y(); // 椭圆中心的 y 坐标
    qreal a = ellipseRect.width() / 2.0; // 椭圆的半长轴
    qreal b = ellipseRect.height() / 2.0; // 椭圆的半短轴

    // 计算点到椭圆中心的相对坐标
    qreal dx = point.x() - x0;
    qreal dy = point.y() - y0;

    // 计算椭圆方程左边的值
    qreal value = (dx * dx) / (a * a) + (dy * dy) / (b * b);

    // 如果左边的值小于等于 1,则点在椭圆内
    return value <= 1.0;
}

 生成Mask图像。

 //ROI更新信号

 roiUpdated(const QVector<Roi_Rect>& rects, const QVector<Roi_Ellipse>& ellipses, const QVector<Roi_Polygon>& polygons);

//Mask更新信号 

maskUpdated(const QImage& mask_image);

void RoiTools::genMask()
{
    //生成默认背景图
    mask->fill(Qt::black);

    gened_roi_rects.clear();
    gened_roi_ellipses.clear();
    gened_roi_polygons.clear();

	QPainter painter(mask);

    if (this->mask_antialiasing)
    {
        painter.setRenderHint(QPainter::Antialiasing);
    }

    //绘制正向选择区域
    //rect
    for (int i = 0; i < roi_rects.count(); i++)
    {
        QRectF m_mask;
        m_mask.setTopLeft(QPointF(roi_rects.at(i).rect.topLeft().x() * pix.width(), roi_rects.at(i).rect.topLeft().y() * pix.height()));
        m_mask.setBottomRight(QPointF(roi_rects.at(i).rect.bottomRight().x() * pix.width(), roi_rects.at(i).rect.bottomRight().y() * pix.height()));

        if (roi_rects.at(i).polarity == Polarity::Positive)
        {
            painter.setPen(Qt::white);
            painter.setBrush(Qt::white);
            painter.drawRect(m_mask);
            Roi_Rect r;
            r.rect = m_mask;
            r.polarity = roi_rects.at(i).polarity;
            gened_roi_rects.append(r);
        }
    }

    //ellp
    for (int i = 0; i < roi_ellipses.count(); i++)
    {
        QRectF m_mask;
        m_mask.setTopLeft(QPointF(roi_ellipses.at(i).rect.topLeft().x() * pix.width(), roi_ellipses.at(i).rect.topLeft().y() * pix.height()));
        m_mask.setBottomRight(QPointF(roi_ellipses.at(i).rect.bottomRight().x() * pix.width(), roi_ellipses.at(i).rect.bottomRight().y() * pix.height()));

        if (roi_ellipses.at(i).polarity == Polarity::Positive)
        {
            painter.setPen(Qt::white);
            painter.setBrush(Qt::white);
            painter.drawEllipse(m_mask);

            Roi_Ellipse e;
            e.rect = m_mask;
            e.polarity = roi_ellipses.at(i).polarity;
            gened_roi_ellipses.append(e);
        }
    }

    //polygon

    for (int i = 0; i < roi_polygons.count(); i++)
    {
        QPolygonF pf;

        for (int j = 0; j < roi_polygons.at(i).polygon.count(); j++)
        {
            QPointF tp(roi_polygons.at(i).polygon.at(j).x() * pix.width(), roi_polygons.at(i).polygon.at(j).y() * pix.height());

            pf.append(tp);
        }

        if (roi_polygons.at(i).polarity == Polarity::Positive)
        {
            painter.setPen(Qt::white);
            painter.setBrush(Qt::white);
            painter.drawPolygon(pf);

            Roi_Polygon p;
            p.polygon = pf;
            p.polarity = roi_polygons.at(i).polarity;
            gened_roi_polygons.append(p);
        }
    }

    //绘制反向抠除区域
    //rect
	for (int i = 0; i < roi_rects.count(); i++)
	{
		QRectF m_mask;
		m_mask.setTopLeft(QPointF(roi_rects.at(i).rect.topLeft().x() * pix.width(), roi_rects.at(i).rect.topLeft().y() * pix.height()));
		m_mask.setBottomRight(QPointF(roi_rects.at(i).rect.bottomRight().x() * pix.width(), roi_rects.at(i).rect.bottomRight().y() * pix.height()));

        if (roi_rects.at(i).polarity == Polarity::Negative)
		{
			painter.setPen(Qt::black);
			painter.setBrush(Qt::black);
            painter.drawRect(m_mask);

            Roi_Rect r;
            r.rect = m_mask;
            r.polarity = roi_rects.at(i).polarity;
            gened_roi_rects.append(r);
		}
	}
    
    //ellp
	for (int i = 0; i < roi_ellipses.count(); i++)
	{
		QRectF m_mask;
		m_mask.setTopLeft(QPointF(roi_ellipses.at(i).rect.topLeft().x() * pix.width(), roi_ellipses.at(i).rect.topLeft().y() * pix.height()));
		m_mask.setBottomRight(QPointF(roi_ellipses.at(i).rect.bottomRight().x() * pix.width(), roi_ellipses.at(i).rect.bottomRight().y() * pix.height()));

        if (roi_ellipses.at(i).polarity == Polarity::Negative)
		{
			painter.setPen(Qt::black);
			painter.setBrush(Qt::black);
            painter.drawEllipse(m_mask);

            Roi_Ellipse e;
            e.rect = m_mask;
            e.polarity = roi_ellipses.at(i).polarity;
            gened_roi_ellipses.append(e);
		}
	}
    
    //polygon
	for (int i = 0; i < roi_polygons.count(); i++)
	{
		QPolygonF pf;

		for (int j = 0; j < roi_polygons.at(i).polygon.count(); j++)
		{
			QPointF tp(roi_polygons.at(i).polygon.at(j).x() * pix.width(), roi_polygons.at(i).polygon.at(j).y() * pix.height());

			pf.append(tp);
		}
        
        if (roi_polygons.at(i).polarity == Polarity::Negative)
		{
			painter.setPen(Qt::black);
			painter.setBrush(Qt::black);
            painter.drawPolygon(pf);

            Roi_Polygon p;
            p.polygon = pf;
            p.polarity = roi_polygons.at(i).polarity;
            gened_roi_polygons.append(p);
		}
	}
    //绘制无极性roi,不进行mask绘制,只输出
    //rect
    for (int i = 0; i < roi_rects.count(); i++)
    {
        QRectF m_mask;
        m_mask.setTopLeft(QPointF(roi_rects.at(i).rect.topLeft().x() * pix.width(), roi_rects.at(i).rect.topLeft().y() * pix.height()));
        m_mask.setBottomRight(QPointF(roi_rects.at(i).rect.bottomRight().x() * pix.width(), roi_rects.at(i).rect.bottomRight().y() * pix.height()));

        if (roi_rects.at(i).polarity == Polarity::None)
        {
            //painter.setPen(Qt::white);
            //painter.setBrush(Qt::white);
            //painter.drawRect(m_mask);
            Roi_Rect r;
            r.rect = m_mask;
            r.polarity = roi_rects.at(i).polarity;
            gened_roi_rects.append(r);
        }
    }

    //ellp
    for (int i = 0; i < roi_ellipses.count(); i++)
    {
        QRectF m_mask;
        m_mask.setTopLeft(QPointF(roi_ellipses.at(i).rect.topLeft().x() * pix.width(), roi_ellipses.at(i).rect.topLeft().y() * pix.height()));
        m_mask.setBottomRight(QPointF(roi_ellipses.at(i).rect.bottomRight().x() * pix.width(), roi_ellipses.at(i).rect.bottomRight().y() * pix.height()));

        if (roi_ellipses.at(i).polarity == Polarity::None)
        {
            //painter.setPen(Qt::white);
            //painter.setBrush(Qt::white);
            //painter.drawEllipse(m_mask);

            Roi_Ellipse e;
            e.rect = m_mask;
            e.polarity = roi_ellipses.at(i).polarity;
            gened_roi_ellipses.append(e);
        }
    }

    //polygon

    for (int i = 0; i < roi_polygons.count(); i++)
    {
        QPolygonF pf;

        for (int j = 0; j < roi_polygons.at(i).polygon.count(); j++)
        {
            QPointF tp(roi_polygons.at(i).polygon.at(j).x() * pix.width(), roi_polygons.at(i).polygon.at(j).y() * pix.height());

            pf.append(tp);
        }

        if (roi_polygons.at(i).polarity == Polarity::None)
        {
            //painter.setPen(Qt::white);
            //painter.setBrush(Qt::white);
            //painter.drawPolygon(pf);

            Roi_Polygon p;
            p.polygon = pf;
            p.polarity = roi_polygons.at(i).polarity;
            gened_roi_polygons.append(p);
        }
    }
	// 结束绘制
	painter.end();

    if (mask_save_path.size() != 0)
    {
        mask->save(mask_save_path);
    }

    emit roiUpdated(gened_roi_rects, gened_roi_ellipses, gened_roi_polygons);

    emit maskUpdated(*mask);
}

四、模块集成

1、将RoiTools.h和RoiTools.cpp添加到Qt工程中。

2、在ui设计器中,将QLabel提升为RoiTools类。

3、调用ui.label_roi_tools->setImage(image),设置图片。

4、绘制ROI。

5、信号槽处理:

QObject::connect(ui.label_roi_tools, &RoiTools::maskUpdated, [=](const QImage& roi_mask) {

		roi_result->setImage(roi_mask);
		roi_mask.save("mask1.bmp");

		});
	
QObject::connect(ui.label_roi_tools, &RoiTools::roiUpdated, [=](const QVector<Roi_Rect>& _t1, const QVector<Roi_Ellipse>& _t2, const QVector<Roi_Polygon>& _t3) {

		qDebug() << _t1.count() << _t2.count() << _t3.count();

		});

;