Draggable Rectangle in OpenCV using MouseCallBack

Hi,

I have a task where I need to draw a rectangle over an image and the rectangle should be draggable using the edges or the sides and also movable over the image. A simple example of what I need is available in Python here: GitHub - arccoder/opencvdragrect: Drag a rectangle on an image window using opencv. I tried to rewrite the code using OpenCV C++. Since I do not have much experience coding with C++ and relatively new to OpenCV, the code apparently is not working. It is displaying the image, but I am not able to draw a rectangle over it with the mouse nor able to control or move anything. I am posting the code here. Any help would be greatly appreciated and valued. Thank you. And please excuse me for the mistakes in the code as I don’t have much experience like I already mentioned.

The code is as below:

#include <iostream>
#include <string>
#include <numeric>
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>


class Rect
{
public:
  int x{};
  int y{};
  int w{};
  int h{};

  Rect()
  {
    std::cout << std::to_string(x) << "," << std::to_string(y) << "," << std::to_string(w) << "," << 
    std::to_string(h) << std::endl;
  }
};


class DragRectangle : Rect
{
public:
   DragRectangle() 
   {
   }
   Rect keepWithin;
   Rect outRect;
   Rect anchor;

   int sBlk = 4;
   bool initialized = false;
   cv::Mat image;
   std::string wname = "";

   bool returnFlag = false;
   bool active = false;
   bool drag = false;

   bool TL = false;
   bool TM = false;
   bool TR = false;
   bool LM = false;
   bool RM = false;
   bool BL = false;
   bool BM = false;
   bool BR = false;
   bool hold = false;
    
   DragRectangle(const cv::Mat Img, const std::string &windowName, const int &windowWidth, const int 
   &windowHeight)
   {
       image = Img;
       wname = windowName;
       keepWithin.x = 0;
       keepWithin.y = 0;
       keepWithin.w = windowWidth;
       keepWithin.h = windowHeight;

       outRect.x = 0;
       outRect.y = 0;
       outRect.w = 0;
       outRect.h = 0;
  }


void ShowWaitDestroy(const char* windowName, cv::Mat image)
{
    cv::imshow(windowName, image);
    cv::moveWindow(windowName, 500, 0);
    cv::waitKey(0);
    cv::destroyWindow(windowName);
}

bool pointInRect(int pX, int pY, int rX, int rY, int rW, int rH)
{
    if (rX <= pX <= (rX + rW) && rY <= pY <= (rY + rH))
    {
        return true;
    }
    else
    {
        return false;
    }
}

void mouseDoubleClick(int eX, int eY, DragRectangle *dragObj)
{
    if (dragObj->active)
    {
        if (pointInRect(eX, eY, dragObj->outRect.x, dragObj->outRect.y, dragObj->outRect.w, dragObj->outRect.h))
        {
            dragObj->returnFlag = true;
            cv::destroyWindow(dragObj->wname);
        }
    }
}

void mouseDown(int eX, int eY, DragRectangle *dragObj)
{
    if (dragObj->active)
    {
        if (pointInRect(eX, eY, dragObj->outRect.x - dragObj->sBlk, dragObj->outRect.y - dragObj->sBlk, dragObj->sBlk * 2, dragObj->sBlk * 2))
            dragObj->TL = true;
        return;
        if (pointInRect(eX, eY, dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk, dragObj->outRect.y - dragObj->sBlk, dragObj->sBlk * 2, dragObj->sBlk * 2))
            dragObj->TR = true;
        return;
        if (pointInRect(eX, eY, dragObj->outRect.x - dragObj->sBlk, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk, dragObj->sBlk * 2, dragObj->sBlk * 2))
            dragObj->BL = true;
        return;
        if (pointInRect(eX, eY, dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk, dragObj->sBlk * 2, dragObj->sBlk * 2))
            dragObj->BR = true;
        return;
        if (pointInRect(eX, eY, dragObj->outRect.x + dragObj->outRect.w / 2 - dragObj->sBlk, dragObj->outRect.y - dragObj->sBlk, dragObj->sBlk * 2, dragObj->sBlk * 2))
            dragObj->TM = true;
        return;
        if (pointInRect(eX, eY, dragObj->outRect.x + dragObj->outRect.w / 2 - dragObj->sBlk, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk, dragObj->sBlk * 2, dragObj->sBlk * 2))
            dragObj->BM = true;
        return;
        if (pointInRect(eX, eY, dragObj->outRect.x - dragObj->sBlk, dragObj->outRect.y + dragObj->outRect.h / 2 - dragObj->sBlk, dragObj->sBlk * 2, dragObj->sBlk * 2))
            dragObj->LM = true;
        return;
        if (pointInRect(eX, eY, dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk, dragObj->outRect.y + dragObj->outRect.h / 2 - dragObj->sBlk, dragObj->sBlk * 2, dragObj->sBlk * 2))
            dragObj->RM = true;
        return;

        if (pointInRect(eX, eY, dragObj->outRect.x, dragObj->outRect.y, dragObj->outRect.w, dragObj->outRect.h))
        {
            dragObj->anchor.x = eX - dragObj->outRect.x;
            dragObj->anchor.w = dragObj->outRect.w - dragObj->anchor.x;
            dragObj->anchor.y = eY - dragObj->outRect.y;
            dragObj->anchor.h = dragObj->outRect.h - dragObj->anchor.y;
            dragObj->hold = true;
            return;
        }
    }
    else
    {
        dragObj->outRect.x = eX;
        dragObj->outRect.y = eY;
        dragObj->drag = true;
        dragObj->active = true;
        return;
    }
}

void mouseMove(int eX, int eY, DragRectangle *dragObj)
{
    if (dragObj->drag && dragObj->active)
    {
        dragObj->outRect.w = eX - dragObj->outRect.x;
        dragObj->outRect.h = eY - dragObj->outRect.y;
        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->hold)
    {
        dragObj->outRect.x = eX - dragObj->anchor.x;
        dragObj->outRect.y = eY - dragObj->anchor.y;

        if (dragObj->outRect.x < dragObj->keepWithin.x)
            dragObj->outRect.x = dragObj->keepWithin.x;
        if (dragObj->outRect.y < dragObj->keepWithin.y)
            dragObj->outRect.y = dragObj->keepWithin.y;
        if ((dragObj->outRect.x + dragObj->outRect.w) > (dragObj->keepWithin.x + dragObj->keepWithin.w - 1))
            dragObj->outRect.x = dragObj->keepWithin.x + dragObj->keepWithin.w - 1 - dragObj->outRect.w;
        if ((dragObj->outRect.y + dragObj->outRect.h) > (dragObj->keepWithin.y + dragObj->keepWithin.h - 1))
            dragObj->outRect.y = dragObj->keepWithin.y + dragObj->keepWithin.h - 1 - dragObj->outRect.h;

        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->TL)
    {
        dragObj->outRect.w = (dragObj->outRect.x + dragObj->outRect.w) - eX;
        dragObj->outRect.h = (dragObj->outRect.y + dragObj->outRect.h) - eY;
        dragObj->outRect.x = eX;
        dragObj->outRect.y = eY;
        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->BR)
    {
        dragObj->outRect.w = eX - dragObj->outRect.x;
        dragObj->outRect.h = eY - dragObj->outRect.y;
        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->TR)
    {
        dragObj->outRect.h = (dragObj->outRect.y + dragObj->outRect.h) - eY;
        dragObj->outRect.y = eY;
        dragObj->outRect.w = eX - dragObj->outRect.x;
        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->BL)
    {
        dragObj->outRect.w = (dragObj->outRect.x + dragObj->outRect.w) - eX;
        dragObj->outRect.x = eX;
        dragObj->outRect.h = eY - dragObj->outRect.y;
        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->TM)
    {
        dragObj->outRect.h = (dragObj->outRect.y + dragObj->outRect.h) - eY;
        dragObj->outRect.y = eY;
        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->BM)
    {
        dragObj->outRect.h = eY - dragObj->outRect.y;
        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->LM)
    {
        dragObj->outRect.w = (dragObj->outRect.x + dragObj->outRect.w) - eX;
        dragObj->outRect.x = eX;
        clearCanvasNDraw(dragObj);
        return;
    }

    if (dragObj->RM)
    {
        dragObj->outRect.w = eX - dragObj->outRect.x;
        clearCanvasNDraw(dragObj);
        return;
    }
}

void mouseUp(DragRectangle *dragObj)
{
    dragObj->drag = false;
    disableResizeButtons(dragObj);
    straightenUpRect(dragObj);
    if (dragObj->outRect.w == 0 || dragObj->outRect.h == 0)
    {
        dragObj->active = false;
    }
    clearCanvasNDraw(dragObj);
}

void disableResizeButtons(DragRectangle *dragObj)
{
    dragObj->TL = dragObj->TM = dragObj->TR = false;
    dragObj->LM = dragObj->RM = false;
    dragObj->BL = dragObj->BM = dragObj->BR = false;
    dragObj->hold = false;
}

void straightenUpRect(DragRectangle *dragObj)
{
    if (dragObj->outRect.w < 0)
    {
        dragObj->outRect.x = dragObj->outRect.x + dragObj->outRect.w;
        dragObj->outRect.w = -dragObj->outRect.w;
    }
    if (dragObj->outRect.h < 0)
    {
        dragObj->outRect.y = dragObj->outRect.y + dragObj->outRect.h;
        dragObj->outRect.h = -dragObj->outRect.h;
    }
}

void clearCanvasNDraw(DragRectangle *dragObj)
{
    cv::Mat tmp = dragObj->image;
    cv::rectangle(tmp, cv::Point((dragObj->outRect.x, dragObj->outRect.y)), cv::Point((dragObj->outRect.x + dragObj->outRect.w, dragObj->outRect.y + dragObj->outRect.h)), (0, 255, 0), 2);
    drawSelectMarkers(tmp, dragObj);
    cv::imshow(dragObj->wname, tmp);
    cv::waitKey();
    cv::destroyWindow(dragObj->wname);
}

void drawSelectMarkers(cv::Mat image, DragRectangle *dragObj)
{
    cv::rectangle(image, cv::Point((dragObj->outRect.x - dragObj->sBlk, dragObj->outRect.y - dragObj->sBlk)), cv::Point((dragObj->outRect.x - dragObj->sBlk + dragObj->sBlk * 2, dragObj->outRect.y - dragObj->sBlk + dragObj->sBlk * 2)), (0, 255, 0), 2);
    cv::rectangle(image, cv::Point((dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk, dragObj->outRect.y - dragObj->sBlk)), cv::Point((dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk + dragObj->sBlk * 2, dragObj->outRect.y - dragObj->sBlk + dragObj->sBlk * 2)), (0, 255, 0), 2);
    cv::rectangle(image, cv::Point((dragObj->outRect.x - dragObj->sBlk, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk)), cv::Point((dragObj->outRect.x - dragObj->sBlk + dragObj->sBlk * 2, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk + dragObj->sBlk * 2)), (0, 255, 0), 2);
    cv::rectangle(image, cv::Point((dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk)), cv::Point((dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk + dragObj->sBlk * 2, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk + dragObj->sBlk * 2)), (0, 255, 0), 2);
    cv::rectangle(image, cv::Point((dragObj->outRect.x + int(dragObj->outRect.w / 2) - dragObj->sBlk, dragObj->outRect.y - dragObj->sBlk)), cv::Point((dragObj->outRect.x + int(dragObj->outRect.w / 2) - dragObj->sBlk + dragObj->sBlk * 2, dragObj->outRect.y - dragObj->sBlk + dragObj->sBlk * 2)), (0, 255, 0), 2);
    cv::rectangle(image, cv::Point((dragObj->outRect.x + int(dragObj->outRect.w / 2) - dragObj->sBlk, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk)), cv::Point((dragObj->outRect.x + int(dragObj->outRect.w / 2) - dragObj->sBlk + dragObj->sBlk * 2, dragObj->outRect.y + dragObj->outRect.h - dragObj->sBlk + dragObj->sBlk * 2)), (0, 255, 0), 2);
    cv::rectangle(image, cv::Point((dragObj->outRect.x - dragObj->sBlk, dragObj->outRect.y + int(dragObj->outRect.h / 2) - dragObj->sBlk)), cv::Point((dragObj->outRect.x - dragObj->sBlk + dragObj->sBlk * 2, dragObj->outRect.y + int(dragObj->outRect.h / 2) - dragObj->sBlk + dragObj->sBlk * 2)), (0, 255, 0), 2);
    cv::rectangle(image, cv::Point((dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk, dragObj->outRect.y + int(dragObj->outRect.h / 2) - dragObj->sBlk)), cv::Point((dragObj->outRect.x + dragObj->outRect.w - dragObj->sBlk + dragObj->sBlk * 2, dragObj->outRect.y + int(dragObj->outRect.h / 2) - dragObj->sBlk + dragObj->sBlk * 2)), (0, 255, 0), 2);
}
  };

 class MouseCallBack : DragRectangle
 {
 public:

static void dragrect(int event, int x, int y, int flags, void *ptr)
{
    DragRectangle *dragObj = (DragRectangle*)ptr;

    if (x < dragObj->keepWithin.x)
    {
        x = dragObj->keepWithin.x;
    }
    if (y < dragObj->keepWithin.y)
    {
        y = dragObj->keepWithin.y;
    }
    if (x < dragObj->keepWithin.x + dragObj->keepWithin.w - 1)
    {
        x = dragObj->keepWithin.x + dragObj->keepWithin.w - 1;
    }
    if (y < dragObj->keepWithin.y + dragObj->keepWithin.y - 1)
    {
        x = dragObj->keepWithin.y + dragObj->keepWithin.y - 1;
    }

    if (event == cv::EVENT_LBUTTONDOWN)
        dragObj->mouseDown(x, y, dragObj);
    if (event == cv::EVENT_LBUTTONUP)
        dragObj->mouseUp(dragObj);
    if (event == cv::EVENT_MOUSEMOVE)
        dragObj->mouseMove(x, y, dragObj);
    if (event == cv::EVENT_LBUTTONDBLCLK)
        dragObj->mouseDoubleClick(x, y, dragObj);
}
};



int main()
{
const std::string windowName = "MyImage";
const std::string filePath = "measure.jpg";

cv::Mat image = cv::imread(filePath);
int imgHeight = image.size().height;
int imgWidth = image.size().width;

DragRectangle *rectI = new DragRectangle(image, windowName, imgHeight, imgWidth);
MouseCallBack m;

cv::namedWindow(windowName);
cv::imshow(windowName, rectI->image);
cv::setMouseCallback(rectI->wname, m.dragrect, &rectI);

while (true)
{
    cv::imshow(windowName, rectI->image);
    int key = cv::waitKey(1);

    if (rectI->returnFlag = true)
        break;
}

std::cout << "Dragged Rectangle Coordinates:" << std::endl;
std::cout << std::to_string(rectI->outRect.x) << "," << std::to_string(rectI->outRect.y) << "," << std::to_string(rectI->outRect.w) << "," << std::to_string(rectI->outRect.h) << std::endl;

cv::destroyAllWindows();
system("pause");
}

Any inputs or help will be appreciated. Thanks in advance.

what is the purpose of this ?

if it’s to initialize a tracker, probably the builtin selectRoi() already does the trick ?

Hi, The purpose is to design a caliper tool where if the rectangle is moved over the image, it should automatically be able to detect edges and give the distance between two edges. Hence, I need this rectangle whose size should also be controllable.

1 Like

yea, but you made a mess of it (state machine from hell !)
and it does not work at all, now ;(

while some problems are simple typos, like here:

every call to cv::destroyAllWindows(); will remove your mouse callback, so remove them all from your code, you do not need it anyway.

again, start fresh & simple. maybe just a fixed size rect, that you can drag around from a single corner. only if that works, go on and improve

also, be aware, that you cannot remove already drawn things from an image, so you probably need to put a clone() of the original image into your DragRectangle struct with each loop iteration

1 Like

Okay. Thank you for your feedback :slight_smile: I will start fresh, step by step, and reach out if it works first, and may be could use suggestions for further improvements.