Shriniwas
Shriniwas

Reputation: 754

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??

Upvotes: 4

Views: 22168

Answers (5)

Vy Phuc
Vy Phuc

Reputation: 1

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

Upvotes: 0

Ahsin Shabbir
Ahsin Shabbir

Reputation: 305

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()

Upvotes: 0

LauriTK
LauriTK

Reputation: 1

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()

Upvotes: 0

Lynx
Lynx

Reputation: 291

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

Upvotes: 0

Innat
Innat

Reputation: 17239

When you move the mouse during event == cv2.EVENT_MOUSEMOVE you also drawing the recatngle at the same time cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),1).

Try following piece of code.

import cv2
import numpy as np

drawing = False # true if mouse is pressed
mode = True # if True, draw rectangle.
ix,iy = -1,-1

# mouse callback function
def draw_circle(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:
        if mode == True:
            cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),3)
            a=x
            b=y
            if a != x | b != y:
                 cv2.rectangle(img,(ix,iy),(x,y),(0,0,0),-1)
        else:
            cv2.circle(img,(x,y),5,(0,0,255),-1)

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

    else:
        cv2.circle(img,(x,y),5,(0,0,255),-1)

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

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

cv2.destroyAllWindows()   

Hope it'll solve your problem. Cheers.

Expected Output:

Rectangle Unfilled Draw


Update


Above piece of code will work only black background image. But we can draw rectangle to any images so -

Try following piece of code.

# import the necessary packages
import cv2
import argparse

# now let's initialize the list of reference point
ref_point = []

def shape_selection(event, x, y, flags, param):
    # grab references to the global variables
    global ref_point, crop

    # if the left mouse button was clicked, record the starting
    # (x, y) coordinates and indicate that cropping is being performed
    if event == cv2.EVENT_LBUTTONDOWN:
        ref_point = [(x, y)]

    # check to see if the left mouse button was released
    elif event == cv2.EVENT_LBUTTONUP:
        # record the ending (x, y) coordinates and indicate that
        # the cropping operation is finished
        ref_point.append((x, y))

        # draw a rectangle around the region of interest
        cv2.rectangle(image, ref_point[0], ref_point[1], (0, 255, 0), 2)
        cv2.imshow("image", image)


# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="Path to the image")
args = vars(ap.parse_args())

# load the image, clone it, and setup the mouse callback function
image = cv2.imread(args["image"])
clone = image.copy()
cv2.namedWindow("image")
cv2.setMouseCallback("image", shape_selection)


# keep looping until the 'q' key is pressed
while True:
    # display the image and wait for a keypress
    cv2.imshow("image", image)
    key = cv2.waitKey(1) & 0xFF

    # press 'r' to reset the window
    if key == ord("r"):
        image = clone.copy()

    # if the 'c' key is pressed, break from the loop
    elif key == ord("c"):
        break

# close all open windows
cv2.destroyAllWindows() 


Save the file as capture_events.py and for testing we selected a demo picture which located at the same directory. Now run the code by following -

python capture_events.py --image demo.jpg

Expected Output:

enter image description here

If some reason we want to re select any portion of the image , we can simply press 'r' to get rid of the bad selection to try a new one.

Hope , it'll will help more. Check this gist, you can implement more functionality on this. Cheers.

Upvotes: 9

Related Questions