'drawing rectangle in openCV python

I am trying to draw rectangle using mouse as input with Opencv in python. I made this code from opencv documentation. There is problem while drawing rectangle that is when you try to drag from the start point rectangle is drawn all the way to end point. Like I showed in Images.

How can I draw clean unfilled rectangle? where I can see drawing an actual rectangle. Like we do in Paint

import cv2
import numpy as np

drawing = False 
ix,iy = -1,-1

def draw_rect(event,x,y,flags,param):
global ix,iy,drawing,mode

if event == cv2.EVENT_LBUTTONDOWN:
    drawing = True
    ix,iy = x,y

elif event == cv2.EVENT_MOUSEMOVE:
    if drawing == True:
            cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),1)


elif event == cv2.EVENT_LBUTTONUP:
    drawing = False
    cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),1)



img = np.zeros((512,512,3), np.uint8)
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_rect)

while(1):
cv2.imshow('image',img)
k = cv2.waitKey(1) & 0xFF
if k == 27:
    break

cv2.destroyAllWindows()

This what happening after executing above code

Can anyone tell me why is that happening? Any solution on it??



Solution 1:[1]

you could change your press mouse event to something like this

elif event == cv2.EVENT_MOUSEMOVE:
if drawing==True:
    copy = image.copy()
    cv2.rectangle(copy,(ix,iy),(x,y),(0,255,0),1)
    cv2.imshow("image", copy)

script will create copies of image with rectangles based on current x's and y's and display real time effect

Solution 2:[2]

I managed to solve this with using copies. This works now with just one imshow() update. Using global variables may not be the best.

import cv2 as cv


drawing = False
ix, iy = -1, -1


def draw_markers(event, x, y, flags, param):
    global ix, iy, drawing, frame, frame_copy
    if flags == cv.EVENT_FLAG_ALTKEY + cv.EVENT_FLAG_LBUTTON:
        if event == cv.EVENT_LBUTTONDOWN:
            print("Alt + lmouse down")
            drawing = True
            ix, iy = x, y
            frame_copy = frame.copy()
        elif event == cv.EVENT_MOUSEMOVE:
            if drawing:
                frame = cv.rectangle(
                    frame_copy.copy(), (ix, iy), (x, y), (0, 255, 0), 2)
        elif event == cv.EVENT_LBUTTONUP:
            print("Alt + lmouse up")
            drawing = False
            cv.rectangle(frame, (ix, iy), (x, y), (0, 255, 0), 2)

    elif event == cv.EVENT_LBUTTONUP:
        print("Draw crosshair")
        cv.drawMarker(frame, (x, y), (255, 0, 0), 0, 16, 2, 8)


cap = cv.VideoCapture('video.avi')
cap.set(cv.CAP_PROP_POS_FRAMES, 1)
ret, frame = cap.read()
frame_copy = frame.copy()
cv.namedWindow('frame')
cv.setMouseCallback('frame', draw_markers)

while(True):
    cv.imshow('frame', frame)
    if cv.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv.destroyAllWindows()

Solution 3:[3]

Building on previous answers, I managed to create a script that can draw rectangles and any size circles on an image. The rectangle updates as you move the mouse around while holding down the left mouse button. The circle gets bigger or smaller by pressing r or t, respectively. Press "m" to toggle between modes. Press "x" to reset the image (remove everything you drew). Here is the code, obviously it can be optimized more and I will be working on that.

import cv2
import numpy as np
drawing = False # true if mouse is pressed
mode = True # if True, draw rectangle. Press 'm' to toggle to curve
ix,iy = -1,-1
x_, y_ = 0,0
r = 15 #circle radius
# mouse callback function
def draw_shape(event,x,y,flags,param):
    print(event)
    global ix,iy,drawing,mode,x_,y_, r

    if event == cv2.EVENT_LBUTTONDOWN:
        print('inside mouse lbutton event....')
        drawing = True
        ix,iy = x,y
        x_,y_ = x,y
    elif event == cv2.EVENT_MOUSEMOVE and drawing:
        copy = img.copy()
        x_,y_ = x,y
        if mode:
            cv2.rectangle(copy,(ix,iy),(x_,y_),(0,255,0),1)
            cv2.imshow("image", copy)
        else:
            cv2.circle(copy,(x,y),r,(0,0,255),1)
            cv2.imshow('image', copy)
    #
    elif event == cv2.EVENT_LBUTTONUP:
        print('inside mouse button up event')
        drawing = False
        if mode:
            cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),1)
        else:
            cv2.circle(img,(x,y),r,(0,0,255),1)


img = np.zeros((512,512,3), np.uint8)
temp_img = np.copy(img)
cv2.namedWindow('image')
cv2.setMouseCallback('image',draw_shape)
while(1):
    # print('inside while loop...')
    cv2.imshow('image',img)
    if not cv2.EVENT_MOUSEMOVE:
        copy = img.copy()
        # print('x_: , y_ : '.format(x_,y_))
        print(x_)
        if mode == True:
            cv2.rectangle(copy,(ix,iy),(x_,y_),(0,255,0),1)
            cv2.imshow('image',copy)
        else:
            cv2.circle(copy,(x_,y_),r,(0,0,255),1)
            cv2.imshow('image',copy)
    k = cv2.waitKey(1) & 0xFF
    if k == ord('m'): #toggle between circle and rectangle
        mode = not mode
        x_,y_ = -10,-10
        ix,iy = -10,-10
    elif k == ord('r') and not mode: #make circle bigger
        r += 1
    elif k == ord('t') and not mode: #make circle smaller
        if r <=2:
            r = 1
        else:
            r -= 1
    elif k == ord('x'): #resets the image (removes circles and rectangles)
        img = np.copy(temp_img)
        x_,y_ = -10,-10
        ix,iy = -10,-10
    elif k == 27:
        break
cv2.destroyAllWindows()

Solution 4:[4]

You will need 2 image variable to draw:

  • draw_img show the image with complete rectangle.
  • tmp_img is the draw_img with current draft for new rectangle.

Here is my code:

  def catch_point(event, x, y, flags, param):
    global draw, cur_x, cur_y, tmp_img, draw_img
    if event == cv.EVENT_LBUTTONDOWN:
        draw = True
        cur_x = x
        cur_y = y
    elif event == cv.EVENT_MOUSEMOVE:
        if draw == True:
            tmp_img = draw_img.copy()
            cv.rectangle(tmp_img, (cur_x, cur_y), (x, y), (36,255,12), 2)
            cv.imshow("draw_img", tmp_img)
    elif event == cv.EVENT_LBUTTONUP:
        draw_img = cv.rectangle(draw_img, (cur_x, cur_y), (x, y), (36,255,12), 2)
        draw = False

  draw_img = img.copy()
  tmp_img = img.copy()
  cv.namedWindow("draw_img", cv.WINDOW_NORMAL)
  cv.setMouseCallback("draw_img", catch_point)
  while True:
      if draw == False:
          cv.imshow("draw_img", draw_img)
      key = cv.waitKey(1) & 0xFF
      if key == ord("s"):
          cv.destroyAllWindows()
          break

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Lynx
Solution 2 LauriTK
Solution 3 Ahsin Shabbir
Solution 4 Vy Phuc