Qt 结合 OpenCV 的 Mat 对象绘制旋转矩形 ROI 区域

文章详细描述了如何在QLabel上使用TB_DrawRotateRect类实现一个可旋转的矩形,包括拖动控制矩形的位置、大小以及通过鼠标操作调整其形状。同时涉及到QScrollArea的图像缩放和Mat类的图像处理。

TB_DrawRotateRect 表示绘制旋转矩形类,在 QLabel 上实现绘制功能。其中,拖到 p0 点能实现旋转矩形的移动;拖到 p1、p2 能实现旋转矩形大小和位置的变化;拖到 p3、p4 能实现旋转矩形长度大小的变化。

// qScrollArea 实现图像的放大缩小显示
QScrollArea* qScrollArea=new QScrollArea(ui->framePicture);
QHBoxLayout* layout=new QHBoxLayout(ui->framePicture);
layout->addWidget(qScrollArea);
//qLabelView 表示图像的显示容器
QLabel* qLabelView=new QLabel(qScrollArea);
qLabelView->setScaledContents(true);
qLabelView->setBackgroundRole(QPalette::Background);
qLabelView->setFrameStyle(QFrame::StyledPanel);
qLabelView->setFrameShadow(QFrame::Sunken);
qLabelView->setGeometry(0, 0, qScrollArea->width(), qScrollArea->height());
qScrollArea->setWidget(qLabelView);
//旋转矩形的参数类
GRotateRectangle rect; 
//Opencv 的图像类
Mat image; 
//qDrawView 表示圆形绘制的对象
TB_DrawRotateRect* qDrawView=new TB_DrawRotateRect(qLabelView, &rect, &image);
qDrawView->setParent(qLabelView);
qDrawView->setWindowFlags(Qt::WindowStaysOnTopHint);
qDrawView->setAttribute(Qt::WA_TranslucentBackground,true);
qDrawView->setStyleSheet("background-color:transparent");

GRotateRectangle 类对象表示旋转矩形的参数对象

#define MOUSE_LENGTH_LEN 10
#define MOUSE_RADIU_LEN  5

//旋转矩形
class GRotateRectangle
{
public:
    GRotateRectangle();
    ~GRotateRectangle(){}
private:
    Point2f p0;
    Point2f p1;
    Point2f p2;
    Point2f p3;
    Point2f p4;
private:
    void UpdataPoint2fApi();
    void Arrow(QPointF p1, QPointF p2, QPointF &p3, QPointF &p4);
public: 
    void SetPosLimit(Size imageSize);
    void GetMinLength(Point2f p, float& length, int& index);
    void DragPointApi(QPointF point, Size imageSize, Size labelSize);
    void DrawApi(QPainter* painter, QColor color1, QColor color2, Size imageSize, Size labelSize);
public:
    Point2f p1_;
    Point2f p2_;
    float measureLength1;
    float measureLength2;
    bool oriented;
    bool arrowed;
};

//初始化
GRotateRectangle::GRotateRectangle()
{
    oriented=false;
    arrowed=false;
    measureLength1=100;
    measureLength2=10;
    p1_=Point2f(200 - measureLength1, 200);
    p2_=Point2f(200 + measureLength1, 200);
    p1=p1_;
    p2=p2_;
}

//更新点状态
void GRotateRectangle::UpdataPoint2fApi()
{
    p0=Point2f((p1.x + p2.x)/2, (p1.y + p2.y)/2);
    p3=Point2f(p0.x, p0.y - measureLength1);
    p4=Point2f(p0.x, p0.y + measureLength1);
    float x=p2.x-p0.x;
    float y=p2.y-p0.y;
    double a=atan2(y, x)*180.0/CV_PI;
    a = a<0 ? (360 + a) : a;
    p3=cvPointRotate(p3, p0, a);
    p4=cvPointRotate(p4, p0, a);
}

//获取最近距离
void GRotateRectangle::GetMinLength(Point2f p, float& length, int& index)
{
    vector<Point2f> sorts;
    sorts.push_back(p0);
    sorts.push_back(p1);
    sorts.push_back(p2);
    sorts.push_back(p3);
    sorts.push_back(p4);
    length=10e9;
    index=-1;
    for(size_t i=0;i<sorts.size();i++)
    {
        float l=cvPointDistance(p, sorts[i]);
        if(l<=length)
        {
            length=l;
            index=(int)i;
        }
    }
}

//箭头
void GRotateRectangle::Arrow(QPointF p1, QPointF p2, QPointF &p3, QPointF &p4)
{
    QLineF line(p1, p2);
    double angle=acos(line.dx()/line.length());
    angle = line.dy()>=0 ? (2.0*QM_PI - angle) : angle;
    float len=QPointDistance(p1, p2) / MOUSE_RADIU_LEN;
    len = len<=1 ? 1 : len;
    p3=p2 + QPointF(sin(angle - QM_PI/3)*len, cos(angle - QM_PI/3)*len);
    p4=p2 + QPointF(sin(angle - QM_PI + QM_PI/3)*len, cos(angle - QM_PI + QM_PI/3)*len);
}

//设置限制
void GRotateRectangle::SetPosLimit(Size imageSize)
{
    int w=imageSize.width;
    int h=imageSize.height;
    if(w==0 || h==0)
        return;
    p1.x = p1.x<0 ? 0 : (p1.x>=w ? w-1 : p1.x);
    p2.x = p2.x<0 ? 0 : (p2.x>=w ? w-1 : p2.x);
    p1.y = p1.y<0 ? 0 : (p1.y>=h ? h-1 : p1.y);
    p2.y = p2.y<0 ? 0 : (p2.y>=h ? h-1 : p2.y);
    if(p1.x==p2.x && p1.y==p2.y && (p1.x==0 || p1.x==w-1) && (p1.y==0 || p1.y==h-1))
    {
        p1=Point2f(w/2 - 10, h/2);
        p2=Point2f(w/2 + 10, h/2);
    }
    float l=(float)sqrt((double)(w*w + h*h))/2;
    measureLength1 = measureLength1>l ? l : measureLength1;
}

//拖动点位置, sX表示图像宽度/控件宽度, sY表示图像高度/控件高度
void GRotateRectangle::DragPointApi(QPointF point, Size imageSize, Size labelSize)
{
    float sX=(float)imageSize.width/labelSize.width;
    float sY=(float)imageSize.height/labelSize.height;
    float sZ=(sX + sY)/2;
    Point2f p(point.x()*sX, point.y()*sY);
    int index;
    float length;
    this->GetMinLength(p, length, index);
    if(length<=(MOUSE_LENGTH_LEN*sZ))
    {
        switch(index)
        {
        case 0:
        {
            float x=p.x-p0.x;
            float y=p.y-p0.y;
            p1=Point2f(p1.x+x, p1.y+y);
            p2=Point2f(p2.x+x, p2.y+y);
            break;
        }
        case 1:
            p1=Point2f(p.x, p.y);
            break;
        case 2:
            p2=Point2f(p.x, p.y);
            break;
        case 3:
        case 4:
            measureLength1=cvPointDistance(p, p0);
            break;
        default:
            break;
        }
    }
    //限制位置尺寸大小
    this->SetPosLimit(imageSize);
}

//绘图
void GRotateRectangle::DrawApi(QPainter* painter, QColor color1, QColor color2, Size imageSize, Size labelSize)
{
    float sX=(float)imageSize.width/labelSize.width;
    float sY=(float)imageSize.height/labelSize.height;
    if(!(labelSize.width>0 && imageSize.width>0))
        return;
    //更新位置点
    this->UpdataPoint2fApi();
    //实际点装换为灰度点
    QPointF P0(p0.x/sX, p0.y/sY);
    QPointF P1(p1.x/sX, p1.y/sY);
    QPointF P2(p2.x/sX, p2.y/sY);
    QPointF P3(p3.x/sX, p3.y/sY);
    QPointF P4(p4.x/sX, p4.y/sY);
    //初始化绘图工具
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setPen(color1);
    //关键点1
    QRectF rect3(P0.x()-MOUSE_RADIU_LEN, P0.y()-MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN);
    painter->drawEllipse(rect3);
    QRectF rect4(P1.x()-MOUSE_RADIU_LEN, P1.y()-MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN);
    painter->drawEllipse(rect4);
    QRectF rect5(P2.x()-MOUSE_RADIU_LEN, P2.y()-MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN);
    painter->drawEllipse(rect5);
    //关键点2
    painter->setPen(color2);
    QRectF rect1(P3.x()-MOUSE_RADIU_LEN, P3.y()-MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN);
    painter->drawEllipse(rect1);
    QRectF rect2(P4.x()-MOUSE_RADIU_LEN, P4.y()-MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN, 2*MOUSE_RADIU_LEN);
    painter->drawEllipse(rect2);
    //边框直线
    QPointF l1=QPointF(P1.x() + P3.x() - P0.x(), P1.y() + P3.y() - P0.y());
    QPointF l2=QPointF(P1.x() + P4.x() - P0.x(), P1.y() + P4.y() - P0.y());
    QPointF l3=QPointF(P2.x() + P4.x() - P0.x(), P2.y() + P4.y() - P0.y());
    QPointF l4=QPointF(P2.x() + P3.x() - P0.x(), P2.y() + P3.y() - P0.y());
    painter->drawLine(l1, l2);
    painter->drawLine(l2, l3);
    painter->drawLine(l3, l4);
    painter->drawLine(l4, l1);
    //判断是否画箭头直线
    if(!arrowed)
        return;
    //画箭头线
    QPointF l5=QPointF((l1.x() + P3.x())/2, (l1.y() + P3.y())/2);
    QPointF l6=QPointF((l2.x() + P4.x())/2, (l2.y() + P4.y())/2);
    QPointF l7=QPointF(P3.x(), P3.y());
    QPointF l8=QPointF(P4.x(), P4.y());
    QPointF l9=QPointF((l4.x() + P3.x())/2, (l4.y() + P3.y())/2);
    QPointF l10=QPointF((l3.x() + P4.x())/2, (l3.y() + P4.y())/2);
    painter->drawLine(l5, l6);
    painter->drawLine(l7, l8);
    painter->drawLine(l9, l10);
    //箭头方向
    QPointF s1 = oriented ? l5 : l6;
    QPointF e1 = oriented ? l6 : l5;
    QPointF s2 = oriented ? l7 : l8;
    QPointF e2 = oriented ? l8 : l7;
    QPointF s3 = oriented ? l9 : l10;
    QPointF e3 = oriented ? l10 : l9;
    QPointF a1, a2, a3, a4, a5, a6;
    this->Arrow(s1, e1, a1, a2);
    this->Arrow(s2, e2, a3, a4);
    this->Arrow(s3, e3, a5, a6);
    painter->drawLine(a1, e1);
    painter->drawLine(a2, e1);
    painter->drawLine(a3, e2);
    painter->drawLine(a4, e2);
    painter->drawLine(a5, e3);
    painter->drawLine(a6, e3);
}

TB_DrawRotateRect 类实现拖到、绘制等功能

class TB_DrawRotateRect : public QWidget
{
    Q_OBJECT
public:
    explicit TB_DrawRotateRect(QWidget* parent, GRotateRectangle* rect, Mat* image);
    void mouseMoveEvent(QMouseEvent *e);
    void mouseReleaseEvent(QMouseEvent *e);
    void paintEvent(QPaintEvent *event);
    ~TB_DrawRotateRect(){}
public:
    GRotateRectangle* pGraphic;
    Mat* pImageSize;
};

//初始化
TB_DrawRotateRect::TB_DrawRotateRect(QWidget* parent, GRotateRectangle* rect, Mat* image) :
    QWidget(parent), pImageSize(image),
    pGraphic(rect)
{}

//鼠标移动
void TB_DrawRotateRect::mouseMoveEvent(QMouseEvent *e)
{
    if(!(e->buttons() & Qt::LeftButton))
        return;
    int x=e->x();
    int y=e->y();
    if(x<0 || y<0 || x>this->width() || y>this->height() || !pImageSize)
        return;
    QPointF point(x, y);
    Size imageSize(pImageSize->cols, pImageSize->rows);
    Size labelSize(this->width(), this->height());
    pGraphic->DragPointApi(point, imageSize, labelSize);
}

//鼠标释放
void TB_DrawRotateRect::mouseReleaseEvent(QMouseEvent *e)
{
    e->accept();
}

//鼠标绘制
void TB_DrawRotateRect::paintEvent(QPaintEvent* event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    if(!pImageSize)
        return;
    Size imageSize(pImageSize->cols, pImageSize->rows);
    Size labelSize(this->width(), this->height());
    pGraphic->DrawApi(&painter, Qt::blue, Qt::red, imageSize, labelSize);
    this->update();
}
<think>我们有两个任务:1) 在图像上绘制矩形;2) 截取矩形区域。根据引用[3]的内容,我们可以使用OpenCV的鼠标回调函数来实现。主要步骤包括: 1. 创建一个窗口并绑定鼠标回调函数。 2. 在回调函数中捕获鼠标事件(如按下、移动、释放)来确定矩形的两个角点。 3. 根据这两个点绘制矩形,并实时显示。 4. 截取矩形区域。 我们将使用OpenCV的highgui模块,但注意在Qt中集成OpenCV时,我们通常使用Qt的窗口系统。不过,引用[3]中使用了OpenCV的窗口。如果要在Qt的界面中实现,我们需要将OpenCV图像转换为Qt的QImage并显示在QLabel上,然后处理Qt的鼠标事件。 根据用户需求,我们有两种实现方式: 方式一:使用纯OpenCV窗口(简单,但不在Qt窗口内) 方式二:在Qt窗口内实现(需要处理Qt的鼠标事件) 由于引用[2]中展示了如何在Qt中显示OpenCV图像(通过QLabel),我们可以采用方式二。 步骤: 1. 将OpenCVMat图像转换为QImage,并显示在QLabel上。 2. 为QLabel安装事件过滤器(或者继承QLabel重写鼠标事件函数),以捕获鼠标事件。 3. 在鼠标事件中记录点,并绘制矩形(可以实时绘制,即拖动鼠标时绘制)。 4. 完成矩形绘制后,截取区域。 这里我们采用方式二,因为用户要求在Qt程序中使用。 具体步骤: 1. 在Qt窗口中放置一个QLabel用于显示图像。 2. 重写QLabel的鼠标事件:mousePressEvent, mouseMoveEvent, mouseReleaseEvent。 3. 在鼠标按下时记录起点,移动时更新终点并重绘(在QLabel上绘制矩形),释放时完成矩形绘制,并截取区域。 注意:由于我们需要在QLabel上实时绘制矩形,我们可以使用两个QPoint变量来记录起点和终点。 另外,我们可以通过QPixmap在QLabel上绘制,但要注意不要破坏原始图像。我们可以保留原始图像,然后在绘制矩形时使用原始图像作为背景,再绘制矩形,然后显示。 或者,我们可以维护两个图像:原始图像和显示图像(用于绘制矩形)。在鼠标移动过程中,我们在原始图像的副本上绘制矩形并显示,当释放后,我们保存矩形区域并重置。 下面是一个简化的步骤: 1. 在MainWindow类(或自定义QLabel类)中定义变量: - QPoint startPoint, endPoint; - bool drawing; // 标记是否正在绘制 - QImage originalImage; // 原始图像(QImage格式) - QImage displayImage; // 用于显示的图像(每次绘制矩形时,从原始图像复制,然后绘制矩形) 2. 在显示图像时,将OpenCVMat转换为QImage(如引用[2]所示),并保存为originalImage,同时显示到QLabel上。 3. 事件处理: - mousePressEvent: 记录startPoint,设置drawing=true。 - mouseMoveEvent: 如果drawing为真,更新endPoint,然后从originalImage复制到displayImage,在displayImage上绘制矩形(使用QPainter),然后更新QLabel。 - mouseReleaseEvent: 设置drawing=false,更新endPoint,然后绘制最后一次矩形(同上),然后截取矩形区域(使用QRect rect(startPoint, endPoint)),然后从originalImage中截取该区域(注意坐标转换,因为QLabel中显示的图像可能经过缩放,所以我们需要将点映射回原始图像坐标)。 注意:由于QLabel可能缩放图像,所以我们在记录的点是相对于QLabel的坐标,而截取时需要的是原始图像坐标。因此,我们需要将点从QLabel的尺寸映射到原始图像尺寸。 映射方法: 原始图像的尺寸:originalImage.size() QLabel中显示的图像尺寸:可能是缩放过且保持宽高比的,所以我们需要计算缩放比例。 假设QLabel显示的图像是原始图像按比例缩放的,且保持宽高比,并且QLabel的scaledContents属性为false(由我们控制缩放显示),那么我们可以通过以下方式计算映射: scale_x = (float)originalImage.width() / ui->label->pixmap()->width(); scale_y = (float)originalImage.height() / ui->label->pixmap()->height(); 但注意:我们的QLabel设置了setScaledContents(true)?引用[2]中设置了true,但这样会导致图像拉伸填充,不保持宽高比。所以建议设置setScaledContents(false)并手动保持宽高比缩放。为了简单,我们假设QLabel显示的图像是保持宽高比的,并且图像在QLabel中居中显示(常见做法)。 由于映射坐标较复杂,另一种做法是:在QLabel中显示时,我们记录下实际显示的图像区域(在QLabel中的位置和大小),然后根据鼠标在QLabel上的位置,减去空白边距,再按显示图像的宽高与原始图像宽高的比例进行缩放。 但为了简化,我们这里假设QLabel显示的图像是原始图像按比例缩放,且没有改变宽高比,并且占满QLabel(setScaledContents(true))但这样会变形。所以最好保持宽高比,并记录实际显示区域。 这里我们采用一个简化方法:在QLabel中显示图像时,使用setPixmap缩放时,我们记录下缩放后的尺寸(如引用[2]中,他们缩放到了250x250,保持宽高比,然后调整了QLabel的大小)。这样,QLabel的大小就是图像显示的大小。因此,我们记录的点就是相对于原始图像按比例缩放的,那么我们可以通过比例反向映射。 具体: 原始图像大小:origWidth, origHeight 显示在QLabel上的图像大小:labelWidth, labelHeight(通过QLabel的size()获取,注意在设置图像后调整了QLabel大小,所以QLabel的大小就是图像显示的大小) 则映射关系: orig_x = (point.x() * origWidth) / labelWidth orig_y = (point.y() * origHeight) / labelHeight 但要注意:如果QLabel的尺寸大于图像显示尺寸(即图像没有填满QLabel,有黑边),那么上面的映射就不准确。所以我们最好让QLabel的尺寸等于图像显示尺寸(如引用[2]中,将QLabel调整为缩放后的图像大小)。 因此,在引用[2]的代码中,他们做了: Qtemp = Qtemp.scaled(250, 250, Qt::KeepAspectRatio, Qt::SmoothTransformation); ui->label->setScaledContents(true); ui->label->resize(Qtemp.size()); 这样QLabel的大小就是图像显示的大小,所以鼠标在QLabel上的坐标就可以按比例映射回原始图像。 步骤: 1. 在打开图像时,我们记录原始图像的尺寸(origSize)和显示在QLabel上的图像尺寸(displaySize,即缩放后的尺寸,也是QLabel的尺寸)。 2. 在鼠标事件中,获取的坐标点(相对于QLabel)可以直接按比例映射到原始图像。 3. 截取时,使用原始图像(originalImage)的copy函数,参数为映射后的矩形区域。 代码结构示例(在自定义QLabel类中实现): 由于我们可能需要多个地方使用,我们可以创建一个继承自QLabel的类,重写鼠标事件。 这里我们以在MainWindow中处理事件为例(简单起见): 在MainWindow.h中: private slots: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; private: QPoint startPoint; QPoint endPoint; bool drawing; QImage originalImage; // 原始图像(QImage) QImage displayImage; // 当前显示的图像(用于绘制矩形) cv::Mat cvImage; // 原始OpenCV图像(如果需要保存) QSize origImageSize; // 原始图像尺寸 在MainWindow.cpp中: 1. 打开图像(如引用[2]): QString testFileName = QFileDialog::getOpenFileName(this, tr(""), current_qpath_, "files(*)"); cv::Mat srcImg = cv::imread(testFileName.toStdString()); cvImage = srcImg.clone(); // 保存原始cv图像 // 转换为QImage cv::cvtColor(srcImg, srcImg, cv::COLOR_BGR2RGB); originalImage = QImage(srcImg.data, srcImg.cols, srcImg.rows, srcImg.step, QImage::Format_RGB888); origImageSize = originalImage.size(); // 缩放图像以在QLabel中显示(保持宽高比,最大250x250) QImage scaledImage = originalImage.scaled(250, 250, Qt::KeepAspectRatio, Qt::SmoothTransformation); // 调整QLabel大小以适应缩放后的图像 ui->label->setPixmap(QPixmap::fromImage(scaledImage)); ui->label->setScaledContents(false); // 这里我们不用setScaledContents(true),因为已经缩放了 ui->label->resize(scaledImage.size()); // 保存缩放后的尺寸,用于坐标映射 // 注意:此时QLabel的大小就是scaledImage.size() 2. 鼠标事件处理: 注意:鼠标事件的坐标是相对于MainWindow的,而我们的QLabel在MainWindow中,所以我们需要将坐标转换到QLabel的坐标系中。 在mousePressEvent中: if (event->button() == Qt::LeftButton && ui->label->underMouse()) { // 获取相对于QLabel的坐标 QPoint pointInLabel = ui->label->mapFrom(this, event->pos()); // 检查是否在图像范围内(因为QLabel可能比图像大,但我们的QLabel大小就是图像大小,所以不用检查) startPoint = pointInLabel; drawing = true; } 在mouseMoveEvent中: if (drawing) { QPoint pointInLabel = ui->label->mapFrom(this, event->pos()); endPoint = pointInLabel; // 更新显示:从originalImage复制,然后绘制矩形 // 注意:在显示图像上绘制,显示图像是缩放后的 QImage temp = originalImage.scaled(ui->label->width(), ui->label->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); // 注意:上面缩放方式应该和之前一致。因为我们之前已经缩放并设置了QLabel大小,所以这里直接使用originalImage缩放到QLabel的大小 // 但实际上,我们之前已经保存了缩放后的图像(scaledImage),但这里我们重新缩放,因为原始图像可能很大,每次缩放效率低。所以我们可以缓存缩放后的图像(不带矩形)和带矩形的图像分开处理。 // 为了简单,我们每次从原始图像缩放(如果图像小可以接受) QPainter painter(&temp); painter.setPen(QPen(Qt::red, 2)); painter.drawRect(QRect(startPoint, endPoint)); painter.end(); ui->label->setPixmap(QPixmap::fromImage(temp)); } 在mouseReleaseEvent中: drawing = false; QPoint pointInLabel = ui->label->mapFrom(this, event->pos()); endPoint = pointInLabel; // 绘制最后一次矩形 // ...(同上) // 计算在原始图像中的矩形区域 // 映射坐标:将QLabel坐标映射到原始图像坐标 double scaleX = (double)origImageSize.width() / ui->label->width(); double scaleY = (double)origImageSize.height() / ui->label->height(); QRect rectInOriginal( QPoint(static_cast<int>(startPoint.x() * scaleX), static_cast<int>(startPoint.y() * scaleY)), QPoint(static_cast<int>(endPoint.x() * scaleX), static_cast<int>(endPoint.y() * scaleY)) ); // 截取原始图像中的矩形区域 QImage cropped = originalImage.copy(rectInOriginal); // 显示截取的部分(例如在另一个QLabel上) // 或者保存 // ... 注意:以上代码是简化示例,实际中需要处理矩形是否有效(比如宽高为负,则要调整),以及绘制矩形时应该允许从任意方向绘制(即起点和终点可能左上角或右下角,所以QRect需要规范化)。 我们可以使用QRect normalized = QRect(startPoint, endPoint).normalized(); 来获得一个左上角到右下角的矩形。 另外,在绘制矩形时,我们使用缩放后的图像(temp)绘制,所以矩形坐标就是QLabel上的坐标,不需要缩放。但截取时,需要将矩形映射回原始图像。 由于性能考虑,频繁缩放原始图像可能会慢,所以我们可以缓存一个不带矩形的缩放图像(scaledOriginal),然后在绘制矩形时,从这个缓存复制,再绘制矩形。这样鼠标移动时就不需要每次都从原始大图像缩放了。 下面我们提供一个更完整的示例代码,包括缓存缩放图像。 步骤: - 在打开图像时,生成一个缩放后的图像(不带矩形)并缓存:scaledOriginal - 在鼠标事件中,从scaledOriginal复制,然后绘制矩形,再设置到QLabel。 修改MainWindow的成员: QImage scaledOriginal; // 缓存缩放后的原始图像(不带矩形) 在打开图像后: // ... 转换originalImage后 scaledOriginal = originalImage.scaled(ui->label->width(), ui->label->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); ui->label->setPixmap(QPixmap::fromImage(scaledOriginal)); 在mouseMoveEvent中: if (drawing) { QPoint pointInLabel = ui->label->mapFrom(this, event->pos()); endPoint = pointInLabel; QImage temp = scaledOriginal.copy(); // 复制一份缓存的缩放图像 QPainter painter(&temp); painter.setPen(QPen(Qt::red, 2)); QRect rect(startPoint, endPoint); painter.drawRect(rect.normalized()); painter.end(); ui->label->setPixmap(QPixmap::fromImage(temp)); } 在mouseReleaseEvent中,同样更新一次,然后截取原始图像。 这样避免了每次从大图缩放。 总结:以上是在Qt结合OpenCV(实际上主要是Qt)实现在图像上绘制矩形并截取区域的方法。注意坐标映射和性能优化。 下面提供一个简化的代码框架(不完整,仅关键部分): </think> ### 使用QtOpenCV在图像上绘制矩形的方法 在QtOpenCV中实现图像绘制矩形功能,主要有两种集成方式:**纯OpenCV实现**和**Qt-OpenCV混合实现**。以下是详细方法和代码示例: --- ### 方法1:纯OpenCV实现(简单快速) 直接在OpenCV窗口中处理鼠标事件,适合不需要复杂UI的场景: ```cpp #include <opencv2/opencv.hpp> cv::Mat image; cv::Point startPt, endPt; bool drawing = false; // 鼠标回调函数 void drawRectangle(int event, int x, int y, int flags, void* param) { cv::Mat img = image.clone(); switch (event) { case cv::EVENT_LBUTTONDOWN: startPt = cv::Point(x, y); drawing = true; break; case cv::EVENT_MOUSEMOVE: if (drawing) { endPt = cv::Point(x, y); cv::rectangle(img, startPt, endPt, cv::Scalar(0, 255, 0), 2); cv::imshow("Image", img); } break; case cv::EVENT_LBUTTONUP: drawing = false; endPt = cv::Point(x, y); cv::rectangle(image, startPt, endPt, cv::Scalar(0, 0, 255), 2); cv::imshow("Image", image); // 截取矩形区域 cv::Mat roi = image(cv::Rect(startPt, endPt)); cv::imwrite("cropped.jpg", roi); break; } } int main() { image = cv::imread("input.jpg"); cv::namedWindow("Image"); cv::setMouseCallback("Image", drawRectangle); cv::imshow("Image", image); cv::waitKey(0); return 0; } ``` --- ### 方法2:Qt-OpenCV混合实现(推荐) 在Qt界面中集成OpenCV,通过QLabel显示图像并处理鼠标事件: ```cpp // 在MainWindow.h中 class MainWindow : public QMainWindow { Q_OBJECT public: // ... protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; private: QPoint startPos, endPos; bool drawing = false; cv::Mat originalImg; QImage displayImg; }; ``` ```cpp // 在MainWindow.cpp中 // 图像加载(参考引用[2]) void MainWindow::loadImage() { QString path = QFileDialog::getOpenFileName(this, "Open Image"); originalImg = cv::imread(path.toStdString()); cv::cvtColor(originalImg, originalImg, cv::COLOR_BGR2RGB); // 转换为QImage并显示 displayImg = QImage(originalImg.data, originalImg.cols, originalImg.rows, originalImg.step, QImage::Format_RGB888); QPixmap pix = QPixmap::fromImage(displayImg) .scaled(ui->label->size(), Qt::KeepAspectRatio); ui->label->setPixmap(pix); } // 鼠标事件处理 void MainWindow::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && ui->label->underMouse()) { startPos = ui->label->mapFromParent(event->pos()); drawing = true; } } void MainWindow::mouseMoveEvent(QMouseEvent *event) { if (drawing) { endPos = ui->label->mapFromParent(event->pos()); QImage temp = displayImg.copy(); QPainter painter(&temp); painter.setPen(QPen(Qt::red, 3)); painter.drawRect(QRect(startPos, endPos)); ui->label->setPixmap(QPixmap::fromImage(temp)); } } void MainWindow::mouseReleaseEvent(QMouseEvent *event) { drawing = false; // 坐标映射回原始图像(关键步骤) QSize labelSize = ui->label->pixmap()->size(); double scaleX = (double)originalImg.cols / labelSize.width(); double scaleY = (double)originalImg.rows / labelSize.height(); cv::Rect roi( (int)(startPos.x() * scaleX), (int)(startPos.y() * scaleY), (int)((endPos.x() - startPos.x()) * scaleX), (int)((endPos.y() - startPos.y()) * scaleY) ); // 截取ROI区域 cv::Mat cropped = originalImg(roi); cv::imwrite("cropped_qt.jpg", cropped); } ``` --- ### 关键要点说明 1. **坐标映射**(Qt实现核心): - 显示在QLabel上的图像通常经过缩放 - 需将鼠标坐标按比例映射回原始图像: $$ \text{origX} = \frac{\text{labelX} \times \text{imageWidth}}{\text{labelWidth}} $$ $$ \text{origY} = \frac{\text{labelY} \times \text{imageHeight}}{\text{labelHeight}} $$ 2. **绘制性能优化**: - 避免直接在原始图像上绘制(使用`QImage::copy()`创建临时副本) - 双缓存机制:始终保留原始图像和显示图像两份数据 3. **OpenCV-Qt转换**: - OpenCV BGR → Qt RGB:`cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB)` - cv::Mat → QImage:基于数据指针直接构造[^2] --- ### 相关问题 1. 如何在Qt中实现图像缩放时的实时矩形绘制? 2. OpenCV的鼠标事件与Qt鼠标事件处理机制有何区别? 3. 如何优化大图像下的矩形绘制性能? 4. 除了矩形,如何实现其他形状(圆形/多边形)的绘制和截取? > 提示:对于复杂需求,可参考OpenCV的`cv::setMouseCallback`机制[^3]或Qt的QGraphicsView框架实现更高级的交互。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刚出道的菜鸟@丢丢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值