Python, drawing a polygon over webcam video using mouse clicks to detect points

Viewed 921

I'm using Python3 and OpenCV (4.1.0) to realize a script that:

  • displays the contents of the webcam;
  • records the coordinates of mouse clicks over video;
  • after pressing a certain button ('p' in my example), draws a polyline between the points identified by previous mouse clicks;

So far, I'm trying:

import numpy as np
import cv2


def main():
    cap = cv2.VideoCapture("files/long_video.mp4")  # Open video file

    points = []
    while (cap.isOpened()):
        ret, frame = cap.read()  # read a frame
        try:
            cv2.imshow('Frame', frame)
        except:
            print('EOF')
            break

        cv2.setMouseCallback('Frame', left_click_detect, points)


        # Abort and exit with 'Q'
        key = cv2.waitKey(25)
        if (key == ord('q')):
            break
        elif (key== ord('p')): # HERE, IT SHOULD DRAW POLYLINE OVER VIDEO!!!
            pts_array = np.array([[x, y] for (x, y) in points], np.int0)
            frame = cv2.polylines(frame, np.int32(np.array(points)), False, (255, 0, 0), thickness=5)
            points = []

        cv2.imshow('Frame', frame)


    cap.release()  # release video file
    cv2.destroyAllWindows()  # close all openCV windows


def left_click(event, x, y, flags, points):
    if (event == cv2.EVENT_LBUTTONDOWN):
        print(f"\tClick on {x}, {y}")
        points.append([x,y])

It kinda works, but after pressing 'p' it doesn't draw the polyline over the video. Any suggestions?

1 Answers

There are 2 problems with your code:

  1. cv2.polylines() accepts a list of arrays. so here:

    frame = cv2.polylines(frame, np.int32(np.array(points)), False, (255, 0, 0), thickness=5)

    Replace np.int32(np.array(points)) with [np.int32(points)] to fix the exception. (you also don't need to use np.array() here)

  2. After you draw the polygon on the frame, you call cv2.show(), but almost immediately after, you show the next frame without the polygon on it, so you don't have time to see the polygon. to fix it you need to draw the polygon again for each frame. and to do that, you need to save it until you press p again (to show another polygon).

This will work:

import numpy as np
import cv2


def main():
    cap = cv2.VideoCapture("files/long_video.mp4")  # Open video file

    polygon = []
    points = []
    while (cap.isOpened()):
        ret, frame = cap.read()  # read a frame
        if not ret:
            print('EOF')
            break

        frame = cv2.polylines(frame, polygon, False, (255, 0, 0), thickness=5)

        cv2.imshow('Frame', frame)
        # Abort and exit with 'Q'
        key = cv2.waitKey(25)
        if (key == ord('q')):
            break
        elif (key== ord('p')): 
            polygon = [np.int32(points)]
            points = []

        cv2.setMouseCallback('Frame', left_click_detect, points)


    cap.release()  # release video file
    cv2.destroyAllWindows()  # close all openCV windows


def left_click_detect(event, x, y, flags, points):
    if (event == cv2.EVENT_LBUTTONDOWN):
        print(f"\tClick on {x}, {y}")
        points.append([x,y])
        print(points)
Related