目录
一、工具功能
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();
});